master
go 288 lines 10.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package vsphere
4
5 import (
6 "context"
7 "strings"
8 "time"
9
10 "github.com/netdata/netdata/go/plugins/pkg/funcapi"
11 topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1"
12 )
13
14 const (
15 topologyMethodID = "topology:vsphere"
16 topologyMethodHelp = "Reports cached vSphere inventory topology for datacenters, clusters, hosts, VMs, datastores, networks, datastore clusters, and resource pools."
17
18 vsphereTopologySource = "vsphere"
19 vsphereTopologyLayer = "virtualization"
20 vsphereTopologyView = "vsphere-inventory"
21
22 vsphereTopologyDetailTable = "vsphere_object_detail"
23 vsphereTopologyLabelsTable = "actor_labels"
24 vsphereTopologyOwnershipLink = "vsphere_ownership"
25 vsphereTopologyRunsOnLink = "vsphere_runs_on"
26 vsphereTopologyNetworkLink = "vsphere_connected_to"
27 vsphereOwnershipEvidenceType = "vsphere_ownership_evidence"
28 vsphereRunsOnEvidenceType = "vsphere_runs_on_evidence"
29 vsphereNetworkConnectionEvidence = "vsphere_network_connection_evidence"
30
31 vsphereDatastoreUtilizationOverlay = "datastore_space_utilization"
32 vsphereOverlaySelectorCollectJob = "collect_job"
33 vsphereOverlaySelectorID = "id"
34 )
35
36 type funcTopology struct {
37 collector *Collector
38 agentID string
39 jobName string
40 }
41
42 var _ funcapi.MethodHandler = (*funcTopology)(nil)
43
44 func vsphereTopologyMethodConfig() funcapi.MethodConfig {
45 return funcapi.MethodConfig{
46 ID: topologyMethodID,
47 Aliases: []string{topologyMethodID},
48 Name: "vSphere Topology",
49 UpdateEvery: 30,
50 Help: topologyMethodHelp,
51 RequireCloud: true,
52 ResponseType: "topology",
53 }
54 }
55
56 func (f *funcTopology) MethodParams(context.Context, string) ([]funcapi.ParamConfig, error) {
57 return nil, nil
58 }
59
60 func (f *funcTopology) Handle(_ context.Context, method string, _ funcapi.ResolvedParams) *funcapi.FunctionResponse {
61 if method != topologyMethodID {
62 return funcapi.NotFoundResponse(method)
63 }
64 if f.collector == nil {
65 return funcapi.UnavailableResponse("collector is not initialized")
66 }
67
68 data, ok := f.collector.topologyData(f.agentID, f.jobName)
69 if !ok {
70 return funcapi.UnavailableResponse("topology data not available yet, please retry after discovery")
71 }
72
73 return &funcapi.FunctionResponse{
74 Status: 200,
75 Help: topologyMethodHelp,
76 ResponseType: "topology",
77 Data: data,
78 }
79 }
80
81 func (f *funcTopology) Cleanup(context.Context) {
82 // No per-invocation resources are allocated by the topology function.
83 }
84
85 func (c *Collector) topologyData(agentID, jobName string) (topologyv1.Data, bool) {
86 c.collectionLock.RLock()
87 defer c.collectionLock.RUnlock()
88
89 if c.resources == nil {
90 return topologyv1.Data{}, false
91 }
92
93 builder := newVSphereTopologyBuilder(jobName)
94
95 for _, dc := range sortedDatacenters(c.resources.DataCenters) {
96 builder.addActor("vsphere_datacenter", dc.ID, dc.Name, "", vsphereActorDetail{
97 objectType: "datacenter",
98 }, nil)
99 }
100 for _, cluster := range sortedClusters(c.resources.Clusters) {
101 builder.addActor("vsphere_cluster", cluster.ID, cluster.Name, cluster.Hier.DC.ID, vsphereActorDetail{
102 objectType: "cluster",
103 datacenter: cluster.Hier.DC.Name,
104 overallStatus: cluster.OverallStatus,
105 drsEnabled: cluster.DrsEnabled,
106 haEnabled: cluster.HaEnabled,
107 vsanEnabled: cluster.VSANEnabled,
108 cpuCapacityMHz: int64(cluster.TotalCpu),
109 memoryCapacityMiB: cluster.TotalMemory / 1024 / 1024,
110 }, mergeStringMaps(cluster.Labels, map[string]string{
111 "datacenter": cluster.Hier.DC.Name,
112 }))
113 if c.resources.DataCenters.Get(cluster.Hier.DC.ID) != nil {
114 builder.addLink(cluster.Hier.DC.ID, cluster.ID, vsphereTopologyOwnershipLink, "contains")
115 }
116 }
117 for _, host := range sortedHosts(c.resources.Hosts) {
118 parentID := firstExistingResourceID(func(id string) bool {
119 if id == host.Hier.Cluster.ID {
120 return c.resources.Clusters.Get(id) != nil
121 }
122 return c.resources.DataCenters.Get(id) != nil
123 }, host.Hier.Cluster.ID, host.Hier.DC.ID)
124 builder.addActor("vsphere_host", host.ID, host.Name, parentID, vsphereActorDetail{
125 objectType: "host",
126 datacenter: host.Hier.DC.Name,
127 cluster: host.Hier.Cluster.Name,
128 connectionState: host.ConnectionState,
129 powerState: host.PowerState,
130 inMaintenanceMode: host.InMaintenanceMode,
131 overallStatus: host.OverallStatus,
132 }, mergeStringMaps(host.Labels, map[string]string{
133 "datacenter": host.Hier.DC.Name,
134 "cluster": host.Hier.Cluster.Name,
135 }))
136 if c.resources.Clusters.Get(host.Hier.Cluster.ID) != nil {
137 builder.addLink(host.Hier.Cluster.ID, host.ID, vsphereTopologyOwnershipLink, "contains")
138 } else if c.resources.DataCenters.Get(host.Hier.DC.ID) != nil {
139 builder.addLink(host.Hier.DC.ID, host.ID, vsphereTopologyOwnershipLink, "contains")
140 }
141 }
142 for _, vm := range sortedVMs(c.resources.VMs) {
143 parentID := firstExistingResourceID(func(id string) bool {
144 switch id {
145 case vm.Hier.Host.ID:
146 return c.resources.Hosts.Get(id) != nil
147 case vm.Hier.Cluster.ID:
148 return c.resources.Clusters.Get(id) != nil
149 default:
150 return c.resources.DataCenters.Get(id) != nil
151 }
152 }, vm.Hier.Host.ID, vm.Hier.Cluster.ID, vm.Hier.DC.ID)
153 builder.addActor("vsphere_vm", vm.ID, vm.Name, parentID, vsphereActorDetail{
154 objectType: "vm",
155 datacenter: vm.Hier.DC.Name,
156 cluster: vm.Hier.Cluster.Name,
157 host: vm.Hier.Host.Name,
158 connectionState: vm.ConnectionState,
159 powerState: vm.PowerState,
160 overallStatus: vm.OverallStatus,
161 toolsRunningStatus: vm.ToolsRunningStatus,
162 toolsVersionStatus: vm.ToolsVersionStatus,
163 consolidationNeeded: vm.ConsolidationNeeded,
164 snapshotCount: vm.SnapshotCount,
165 snapshotChainDepth: vm.SnapshotMaxChainDepth,
166 configuredVCPUs: vm.ConfigCPU,
167 configuredMemoryMiB: vm.ConfigMemory,
168 }, mergeStringMaps(vm.Labels, map[string]string{
169 "datacenter": vm.Hier.DC.Name,
170 "cluster": vm.Hier.Cluster.Name,
171 "host": vm.Hier.Host.Name,
172 }))
173 switch {
174 case c.resources.Hosts.Get(vm.Hier.Host.ID) != nil:
175 builder.addLink(vm.ID, vm.Hier.Host.ID, vsphereTopologyRunsOnLink, "runs_on")
176 case c.resources.Clusters.Get(vm.Hier.Cluster.ID) != nil:
177 builder.addLink(vm.Hier.Cluster.ID, vm.ID, vsphereTopologyOwnershipLink, "contains")
178 case c.resources.DataCenters.Get(vm.Hier.DC.ID) != nil:
179 builder.addLink(vm.Hier.DC.ID, vm.ID, vsphereTopologyOwnershipLink, "contains")
180 }
181 }
182 for _, datastore := range sortedDatastores(c.resources.Datastores) {
183 actor := builder.addActor("vsphere_datastore", datastore.ID, datastore.Name, datastore.Hier.DC.ID, vsphereActorDetail{
184 objectType: "datastore",
185 datacenter: datastore.Hier.DC.Name,
186 overallStatus: datastore.OverallStatus,
187 datastoreType: datastore.Type,
188 accessible: datastore.Accessible,
189 maintenanceMode: datastore.MaintenanceMode,
190 capacityBytes: datastore.Capacity,
191 freeSpaceBytes: datastore.FreeSpace,
192 uncommittedBytes: datastore.Uncommitted,
193 multipleHostAccess: optionalBool(datastore.MultipleHostAccess),
194 }, mergeStringMaps(datastore.Labels, map[string]string{
195 "datacenter": datastore.Hier.DC.Name,
196 "type": datastore.Type,
197 }))
198 builder.addDatastoreUtilizationOverlay(actor, datastore.ID)
199 if c.resources.DataCenters.Get(datastore.Hier.DC.ID) != nil {
200 builder.addLink(datastore.Hier.DC.ID, datastore.ID, vsphereTopologyOwnershipLink, "contains")
201 }
202 }
203 for _, network := range sortedNetworks(c.resources.Networks) {
204 builder.addActor("vsphere_network", network.ID, network.Name, network.Hier.DC.ID, vsphereActorDetail{
205 objectType: "network",
206 datacenter: network.Hier.DC.Name,
207 networkType: network.Type,
208 accessible: network.Accessible,
209 ipPoolName: network.IPPoolName,
210 overallStatus: network.OverallStatus,
211 networkHosts: len(network.HostIDs),
212 networkVMs: len(network.VMIDs),
213 }, mergeStringMaps(network.Labels, map[string]string{
214 "datacenter": network.Hier.DC.Name,
215 "type": network.Type,
216 }))
217 if c.resources.DataCenters.Get(network.Hier.DC.ID) != nil {
218 builder.addLink(network.Hier.DC.ID, network.ID, vsphereTopologyOwnershipLink, "contains")
219 }
220 for _, hostID := range sortedStrings(network.HostIDs) {
221 if c.resources.Hosts.Get(hostID) != nil {
222 builder.addLink(hostID, network.ID, vsphereTopologyNetworkLink, "connected_to")
223 }
224 }
225 for _, vmID := range sortedStrings(network.VMIDs) {
226 if c.resources.VMs.Get(vmID) != nil {
227 builder.addLink(vmID, network.ID, vsphereTopologyNetworkLink, "connected_to")
228 }
229 }
230 }
231 for _, pod := range sortedStoragePods(c.resources.StoragePods) {
232 builder.addActor("vsphere_datastore_cluster", pod.ID, pod.Name, pod.Hier.DC.ID, vsphereActorDetail{
233 objectType: "datastore_cluster",
234 datacenter: pod.Hier.DC.Name,
235 overallStatus: pod.OverallStatus,
236 capacityBytes: pod.Capacity,
237 freeSpaceBytes: pod.FreeSpace,
238 storageDRSEnabled: optionalBool(pod.StorageDRSEnabled),
239 }, mergeStringMaps(pod.Labels, map[string]string{
240 "datacenter": pod.Hier.DC.Name,
241 }))
242 if c.resources.DataCenters.Get(pod.Hier.DC.ID) != nil {
243 builder.addLink(pod.Hier.DC.ID, pod.ID, vsphereTopologyOwnershipLink, "contains")
244 }
245 }
246 for _, pool := range sortedResourcePools(c.resources.ResourcePools) {
247 builder.addActor("vsphere_resource_pool", pool.ID, pool.Name, pool.Hier.Cluster.ID, vsphereActorDetail{
248 objectType: "resource_pool",
249 datacenter: pool.Hier.DC.Name,
250 cluster: pool.Hier.Cluster.Name,
251 resourcePool: pool.Name,
252 overallStatus: pool.OverallStatus,
253 cpuLimitMHz: pool.CpuLimit,
254 memoryLimitMiB: pool.MemLimit,
255 cpuReservation: pool.CpuReservation,
256 memReservation: pool.MemReservation,
257 }, mergeStringMaps(pool.Labels, map[string]string{
258 "datacenter": pool.Hier.DC.Name,
259 "cluster": pool.Hier.Cluster.Name,
260 "resource_pool": pool.Name,
261 }))
262 if c.resources.Clusters.Get(pool.Hier.Cluster.ID) != nil {
263 builder.addLink(pool.Hier.Cluster.ID, pool.ID, vsphereTopologyOwnershipLink, "contains")
264 }
265 }
266
267 data, err := builder.data(strings.TrimSpace(agentID), time.Now().UTC(), map[string]any{
268 "datacenters": len(c.resources.DataCenters),
269 "clusters": len(c.resources.Clusters),
270 "hosts": len(c.resources.Hosts),
271 "vms": len(c.resources.VMs),
272 "datastores": len(c.resources.Datastores),
273 "networks": len(c.resources.Networks),
274 "datastore_clusters": len(c.resources.StoragePods),
275 "resource_pools": len(c.resources.ResourcePools),
276 })
277 if err != nil {
278 return topologyv1.Data{}, false
279 }
280 return data, true
281 }
282
283 func optionalBool(value *bool) any {
284 if value == nil {
285 return nil
286 }
287 return *value
288 }