| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package discover |
| 4 | |
| 5 | import ( |
| 6 | "time" |
| 7 | |
| 8 | rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources" |
| 9 | |
| 10 | "github.com/vmware/govmomi/vim25/mo" |
| 11 | "github.com/vmware/govmomi/vim25/types" |
| 12 | ) |
| 13 | |
| 14 | func (d Discoverer) build(raw *resources) *rs.Resources { |
| 15 | d.Debug("discovering : building : starting building resources process") |
| 16 | t := time.Now() |
| 17 | |
| 18 | var res rs.Resources |
| 19 | res.DataCenters = d.buildDatacenters(raw.dcs) |
| 20 | res.Folders = d.buildFolders(raw.folders) |
| 21 | res.Clusters = d.buildClusters(raw.clusters) |
| 22 | fixClustersParentID(&res) |
| 23 | res.Hosts = d.buildHosts(raw.hosts) |
| 24 | res.VMs = d.buildVMs(raw.vms) |
| 25 | res.Datastores = d.buildDatastores(raw.datastores) |
| 26 | res.Networks = d.buildNetworks(raw.networks) |
| 27 | res.StoragePods = d.buildStoragePods(raw.storagePods) |
| 28 | res.ResourcePools = d.buildResourcePools(raw.resourcePools, res.Clusters) |
| 29 | |
| 30 | d.Infof("discovering : building : built %d/%d dcs, %d/%d folders, %d/%d clusters, %d/%d hosts, %d/%d vms, %d/%d datastores, %d/%d networks, %d/%d datastore clusters, %d/%d resource pools, process took %s", |
| 31 | len(res.DataCenters), |
| 32 | len(raw.dcs), |
| 33 | len(res.Folders), |
| 34 | len(raw.folders), |
| 35 | len(res.Clusters), |
| 36 | len(raw.clusters), |
| 37 | len(res.Hosts), |
| 38 | len(raw.hosts), |
| 39 | len(res.VMs), |
| 40 | len(raw.vms), |
| 41 | len(res.Datastores), |
| 42 | len(raw.datastores), |
| 43 | len(res.Networks), |
| 44 | len(raw.networks), |
| 45 | len(res.StoragePods), |
| 46 | len(raw.storagePods), |
| 47 | len(res.ResourcePools), |
| 48 | len(raw.resourcePools), |
| 49 | time.Since(t), |
| 50 | ) |
| 51 | return &res |
| 52 | } |
| 53 | |
| 54 | // cluster parent is folder by default |
| 55 | // should be called after buildDatacenters, buildFolders and buildClusters |
| 56 | func fixClustersParentID(res *rs.Resources) { |
| 57 | for _, c := range res.Clusters { |
| 58 | c.ParentID = findClusterDcID(c.ParentID, res.Folders) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | func findClusterDcID(parentID string, folders rs.Folders) string { |
| 63 | return findFolderRootID(parentID, folders) |
| 64 | } |
| 65 | |
| 66 | func (Discoverer) buildDatacenters(raw []mo.Datacenter) rs.DataCenters { |
| 67 | dcs := make(rs.DataCenters) |
| 68 | for _, d := range raw { |
| 69 | dcs.Put(newDC(d)) |
| 70 | } |
| 71 | return dcs |
| 72 | } |
| 73 | |
| 74 | func newDC(raw mo.Datacenter) *rs.Datacenter { |
| 75 | // Datacenter1 datacenter-2 group-h4 group-v3 |
| 76 | return &rs.Datacenter{ |
| 77 | Name: raw.Name, |
| 78 | ID: raw.Reference().Value, |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | func (Discoverer) buildFolders(raw []mo.Folder) rs.Folders { |
| 83 | fs := make(rs.Folders) |
| 84 | for _, d := range raw { |
| 85 | if f := newFolder(d); f != nil { |
| 86 | fs.Put(f) |
| 87 | } |
| 88 | } |
| 89 | return fs |
| 90 | } |
| 91 | |
| 92 | func newFolder(raw mo.Folder) *rs.Folder { |
| 93 | parentID := parentRefValue(raw.Parent) |
| 94 | if parentID == "" { |
| 95 | return nil |
| 96 | } |
| 97 | // vm group-v55 datacenter-54 |
| 98 | // host group-h56 datacenter-54 |
| 99 | // datastore group-s57 datacenter-54 |
| 100 | // network group-n58 datacenter-54 |
| 101 | return &rs.Folder{ |
| 102 | Name: raw.Name, |
| 103 | ID: raw.Reference().Value, |
| 104 | ParentID: parentID, |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | func (Discoverer) buildClusters(raw []mo.ComputeResource) rs.Clusters { |
| 109 | clusters := make(rs.Clusters) |
| 110 | for _, c := range raw { |
| 111 | if cluster := newCluster(c); cluster != nil { |
| 112 | clusters.Put(cluster) |
| 113 | } |
| 114 | } |
| 115 | return clusters |
| 116 | } |
| 117 | |
| 118 | func newCluster(raw mo.ComputeResource) *rs.Cluster { |
| 119 | parentID := parentRefValue(raw.Parent) |
| 120 | if parentID == "" { |
| 121 | return nil |
| 122 | } |
| 123 | // s - dummy cluster, c - created by user cluster |
| 124 | // 192.168.0.201 domain-s61 group-h4 |
| 125 | // New Cluster1 domain-c52 group-h67 |
| 126 | cluster := &rs.Cluster{ |
| 127 | Name: raw.Name, |
| 128 | ID: raw.Reference().Value, |
| 129 | ParentID: parentID, |
| 130 | CustomValues: customFieldValues(raw.CustomValue), |
| 131 | Ref: raw.Reference(), |
| 132 | } |
| 133 | rs.SetClusterVSANInfo(cluster, raw.ConfigurationEx) |
| 134 | return cluster |
| 135 | } |
| 136 | |
| 137 | func (d Discoverer) buildHosts(raw []mo.HostSystem) rs.Hosts { |
| 138 | hosts := make(rs.Hosts) |
| 139 | for _, h := range raw { |
| 140 | if host := newHost(h); host != nil { |
| 141 | hosts.Put(host) |
| 142 | } |
| 143 | } |
| 144 | return hosts |
| 145 | } |
| 146 | |
| 147 | func newHost(raw mo.HostSystem) *rs.Host { |
| 148 | parentID := parentRefValue(raw.Parent) |
| 149 | if parentID == "" { |
| 150 | return nil |
| 151 | } |
| 152 | // 192.168.0.201 host-22 domain-s61 |
| 153 | // 192.168.0.202 host-28 domain-c52 |
| 154 | // 192.168.0.203 host-33 domain-c52 |
| 155 | host := &rs.Host{ |
| 156 | Name: raw.Name, |
| 157 | ID: raw.Reference().Value, |
| 158 | ParentID: parentID, |
| 159 | CustomValues: customFieldValues(raw.CustomValue), |
| 160 | ConnectionState: string(raw.Runtime.ConnectionState), |
| 161 | PowerState: string(raw.Runtime.PowerState), |
| 162 | InMaintenanceMode: raw.Runtime.InMaintenanceMode, |
| 163 | OverallStatus: string(raw.Summary.OverallStatus), |
| 164 | Ref: raw.Reference(), |
| 165 | } |
| 166 | if raw.Config != nil && raw.Config.VsanHostConfig != nil && raw.Config.VsanHostConfig.ClusterInfo != nil { |
| 167 | host.VSANNodeUUID = raw.Config.VsanHostConfig.ClusterInfo.NodeUuid |
| 168 | } |
| 169 | return host |
| 170 | } |
| 171 | |
| 172 | func (d Discoverer) buildVMs(raw []mo.VirtualMachine) rs.VMs { |
| 173 | vms := make(rs.VMs) |
| 174 | for _, v := range raw { |
| 175 | vms.Put(newVM(v)) |
| 176 | } |
| 177 | return vms |
| 178 | } |
| 179 | |
| 180 | func newVM(raw mo.VirtualMachine) *rs.VM { |
| 181 | // deb91 vm-25 group-v3 host-22 |
| 182 | var hostID string |
| 183 | if raw.Runtime.Host != nil { |
| 184 | hostID = raw.Runtime.Host.Value |
| 185 | } |
| 186 | var folderID string |
| 187 | if raw.Parent != nil { |
| 188 | folderID = raw.Parent.Value |
| 189 | } |
| 190 | var toolsRunningStatus, toolsVersionStatus string |
| 191 | if raw.Summary.Guest != nil { |
| 192 | toolsRunningStatus = raw.Summary.Guest.ToolsRunningStatus |
| 193 | toolsVersionStatus = raw.Summary.Guest.ToolsVersionStatus2 |
| 194 | } |
| 195 | var committed, uncommitted, unshared int64 |
| 196 | if raw.Summary.Storage != nil { |
| 197 | committed = raw.Summary.Storage.Committed |
| 198 | uncommitted = raw.Summary.Storage.Uncommitted |
| 199 | unshared = raw.Summary.Storage.Unshared |
| 200 | } |
| 201 | var instanceUUID string |
| 202 | if raw.Config != nil { |
| 203 | instanceUUID = raw.Config.InstanceUuid |
| 204 | } |
| 205 | snapshot := summarizeSnapshotInfo(raw.Snapshot) |
| 206 | return &rs.VM{ |
| 207 | Name: raw.Name, |
| 208 | ID: raw.Reference().Value, |
| 209 | ParentID: hostID, |
| 210 | FolderParentID: folderID, |
| 211 | CustomValues: customFieldValues(raw.CustomValue), |
| 212 | ConnectionState: string(raw.Runtime.ConnectionState), |
| 213 | PowerState: string(raw.Runtime.PowerState), |
| 214 | ToolsRunningStatus: toolsRunningStatus, |
| 215 | ToolsVersionStatus: toolsVersionStatus, |
| 216 | InstanceUUID: instanceUUID, |
| 217 | ConsolidationNeeded: raw.Runtime.ConsolidationNeeded, |
| 218 | ConfigCPU: int64(raw.Summary.Config.NumCpu), |
| 219 | ConfigMemory: int64(raw.Summary.Config.MemorySizeMB), |
| 220 | ConfigDisks: int64(raw.Summary.Config.NumVirtualDisks), |
| 221 | ConfigNICs: int64(raw.Summary.Config.NumEthernetCards), |
| 222 | StorageCommitted: committed, |
| 223 | StorageUncommitted: uncommitted, |
| 224 | StorageUnshared: unshared, |
| 225 | OverallStatus: string(raw.Summary.OverallStatus), |
| 226 | SnapshotCount: snapshot.count, |
| 227 | SnapshotMaxChainDepth: snapshot.maxChainDepth, |
| 228 | SnapshotOldestCreateTime: snapshot.oldestCreateTime, |
| 229 | Ref: raw.Reference(), |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | func (d Discoverer) buildDatastores(raw []mo.Datastore) rs.Datastores { |
| 234 | datastores := make(rs.Datastores) |
| 235 | for _, ds := range raw { |
| 236 | if datastore := newDatastore(ds); datastore != nil { |
| 237 | datastores.Put(datastore) |
| 238 | } |
| 239 | } |
| 240 | return datastores |
| 241 | } |
| 242 | |
| 243 | func newDatastore(raw mo.Datastore) *rs.Datastore { |
| 244 | parentID := parentRefValue(raw.Parent) |
| 245 | if parentID == "" { |
| 246 | return nil |
| 247 | } |
| 248 | return &rs.Datastore{ |
| 249 | Name: raw.Name, |
| 250 | ID: raw.Reference().Value, |
| 251 | ParentID: parentID, |
| 252 | CustomValues: customFieldValues(raw.CustomValue), |
| 253 | OverallStatus: string(raw.OverallStatus), |
| 254 | Type: raw.Summary.Type, |
| 255 | Capacity: raw.Summary.Capacity, |
| 256 | FreeSpace: raw.Summary.FreeSpace, |
| 257 | Uncommitted: raw.Summary.Uncommitted, |
| 258 | Accessible: raw.Summary.Accessible, |
| 259 | MaintenanceMode: raw.Summary.MaintenanceMode, |
| 260 | MultipleHostAccess: raw.Summary.MultipleHostAccess, |
| 261 | Ref: raw.Reference(), |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | func (d Discoverer) buildNetworks(raw []mo.Network) rs.Networks { |
| 266 | networks := make(rs.Networks) |
| 267 | for _, network := range raw { |
| 268 | networks.Put(newNetwork(network)) |
| 269 | } |
| 270 | return networks |
| 271 | } |
| 272 | |
| 273 | func newNetwork(raw mo.Network) *rs.Network { |
| 274 | var accessible bool |
| 275 | var ipPoolName string |
| 276 | if raw.Summary != nil { |
| 277 | if summary := raw.Summary.GetNetworkSummary(); summary != nil { |
| 278 | accessible = summary.Accessible |
| 279 | ipPoolName = summary.IpPoolName |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | networkType := raw.Reference().Type |
| 284 | if networkType == "" { |
| 285 | networkType = "Network" |
| 286 | } |
| 287 | |
| 288 | var parentID string |
| 289 | if raw.Parent != nil { |
| 290 | parentID = raw.Parent.Value |
| 291 | } |
| 292 | |
| 293 | return &rs.Network{ |
| 294 | Name: raw.Name, |
| 295 | ID: raw.Reference().Value, |
| 296 | Type: networkType, |
| 297 | ParentID: parentID, |
| 298 | CustomValues: customFieldValues(raw.CustomValue), |
| 299 | Accessible: accessible, |
| 300 | IPPoolName: ipPoolName, |
| 301 | HostIDs: refValues(raw.Host), |
| 302 | VMIDs: refValues(raw.Vm), |
| 303 | OverallStatus: string(raw.OverallStatus), |
| 304 | Ref: raw.Reference(), |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | func refValues(refs []types.ManagedObjectReference) []string { |
| 309 | if len(refs) == 0 { |
| 310 | return nil |
| 311 | } |
| 312 | values := make([]string, 0, len(refs)) |
| 313 | for _, ref := range refs { |
| 314 | if ref.Value != "" { |
| 315 | values = append(values, ref.Value) |
| 316 | } |
| 317 | } |
| 318 | return values |
| 319 | } |
| 320 | |
| 321 | func (d Discoverer) buildStoragePods(raw []mo.StoragePod) rs.StoragePods { |
| 322 | pods := make(rs.StoragePods) |
| 323 | for _, pod := range raw { |
| 324 | if storagePod := newStoragePod(pod); storagePod != nil { |
| 325 | pods.Put(storagePod) |
| 326 | } |
| 327 | } |
| 328 | return pods |
| 329 | } |
| 330 | |
| 331 | func newStoragePod(raw mo.StoragePod) *rs.StoragePod { |
| 332 | parentID := parentRefValue(raw.Parent) |
| 333 | if parentID == "" { |
| 334 | return nil |
| 335 | } |
| 336 | var capacity, freeSpace int64 |
| 337 | if raw.Summary != nil { |
| 338 | capacity = raw.Summary.Capacity |
| 339 | freeSpace = raw.Summary.FreeSpace |
| 340 | } |
| 341 | var storageDRSEnabled *bool |
| 342 | if raw.PodStorageDrsEntry != nil { |
| 343 | storageDRSEnabled = new(raw.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Enabled) |
| 344 | } |
| 345 | return &rs.StoragePod{ |
| 346 | Name: raw.Name, |
| 347 | ID: raw.Reference().Value, |
| 348 | ParentID: parentID, |
| 349 | CustomValues: customFieldValues(raw.CustomValue), |
| 350 | Capacity: capacity, |
| 351 | FreeSpace: freeSpace, |
| 352 | StorageDRSEnabled: storageDRSEnabled, |
| 353 | OverallStatus: string(raw.OverallStatus), |
| 354 | Ref: raw.Reference(), |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | func (d Discoverer) buildResourcePools(raw []mo.ResourcePool, clusters rs.Clusters) rs.ResourcePools { |
| 359 | pools := make(rs.ResourcePools) |
| 360 | for _, rp := range raw { |
| 361 | // owner is the cluster that owns this pool |
| 362 | ownerID := rp.Owner.Value |
| 363 | if ownerID == "" { |
| 364 | continue |
| 365 | } |
| 366 | // skip pools whose owner is a dummy cluster (standalone host) |
| 367 | if isDummyCluster(ownerID) { |
| 368 | continue |
| 369 | } |
| 370 | // skip pools whose owner cluster wasn't discovered |
| 371 | if clusters.Get(ownerID) == nil { |
| 372 | continue |
| 373 | } |
| 374 | pools.Put(newResourcePool(rp)) |
| 375 | } |
| 376 | return pools |
| 377 | } |
| 378 | |
| 379 | func newResourcePool(raw mo.ResourcePool) *rs.ResourcePool { |
| 380 | return &rs.ResourcePool{ |
| 381 | Name: raw.Name, |
| 382 | ID: raw.Reference().Value, |
| 383 | ParentID: raw.Owner.Value, // owner cluster ref |
| 384 | CustomValues: customFieldValues(raw.CustomValue), |
| 385 | Ref: raw.Reference(), |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | func customFieldValues(values []types.BaseCustomFieldValue) map[int32]string { |
| 390 | if len(values) == 0 { |
| 391 | return nil |
| 392 | } |
| 393 | |
| 394 | out := make(map[int32]string, len(values)) |
| 395 | for _, value := range values { |
| 396 | stringValue, ok := value.(*types.CustomFieldStringValue) |
| 397 | if !ok || stringValue == nil || stringValue.Value == "" { |
| 398 | continue |
| 399 | } |
| 400 | out[stringValue.Key] = stringValue.Value |
| 401 | } |
| 402 | if len(out) == 0 { |
| 403 | return nil |
| 404 | } |
| 405 | return out |
| 406 | } |
| 407 | |
| 408 | func parentRefValue(ref *types.ManagedObjectReference) string { |
| 409 | if ref == nil { |
| 410 | return "" |
| 411 | } |
| 412 | return ref.Value |
| 413 | } |