diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 5b3c4f6faea..8a97739a541 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -333,6 +333,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] - Add new metrics for the vSphere Virtualmachine metricset. {pull}40485[40485] - Add support for snapshot in vSphere virtualmachine metricset {pull}40683[40683] - Update fields to use mapstr in vSphere virtualmachine metricset {pull}40707[40707] +- Add metrics related to alert in all the vSphere metricsets. {pull}40714[40714] - Add support for period based intervalID in vSphere host and datastore metricsets {pull}40678[40678] - Add new metrics fot datastore and minor changes to overall vSphere metrics {pull}40766[40766] - Add `metrics_count` to Prometheus module if `metrics_count: true` is set. {pull}40411[40411] diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 11c7ca496e0..98700586a9b 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -66965,6 +66965,16 @@ type: keyword -- +*`vsphere.cluster.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + [float] === datastore @@ -67118,6 +67128,16 @@ type: keyword -- +*`vsphere.datastore.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + *`vsphere.datastore.vm.count`*:: + -- @@ -67211,6 +67231,16 @@ type: long -- +*`vsphere.datastorecluster.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + [float] === host @@ -67568,6 +67598,16 @@ type: keyword -- +*`vsphere.host.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + *`vsphere.host.uptime`*:: + -- @@ -67697,6 +67737,16 @@ type: long -- +*`vsphere.network.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + [float] === resourcepool @@ -67923,6 +67973,16 @@ type: keyword -- +*`vsphere.resourcepool.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + [float] === virtualmachine @@ -68150,6 +68210,16 @@ type: long -- +*`vsphere.virtualmachine.triggerd_alarms.*`*:: ++ +-- +List of all the triggerd alarms. + + +type: object + +-- + [[exported-fields-windows]] == Windows fields diff --git a/metricbeat/module/vsphere/cluster/_meta/data.json b/metricbeat/module/vsphere/cluster/_meta/data.json index 6e165063e85..7c8c435c0d9 100644 --- a/metricbeat/module/vsphere/cluster/_meta/data.json +++ b/metricbeat/module/vsphere/cluster/_meta/data.json @@ -1,47 +1,81 @@ { - "@timestamp": "2017-10-12T08:05:34.853Z", - "event": { - "dataset": "vsphere.cluster", - "duration": 115000, - "module": "vsphere" - }, - "metricset": { - "name": "cluster", - "period": 10000 - }, - "service": { - "address": "127.0.0.1:33365", - "type": "vsphere" - }, - "vsphere": { + "@timestamp": "2017-10-12T08:05:34.853Z", + "event": { + "dataset": "vsphere.cluster", + "duration": 115000, + "module": "vsphere" + }, + "metricset": { + "name": "cluster", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:33365", + "type": "vsphere" + }, + "vsphere": { "cluster": { - "name": "Cluster_1", - "das_config": { - "enabled": false, - "admission": { - "control": { - "enabled": true - } + "triggerd_alarms": [ + { + "status": "red", + "triggered_time": "2024-09-09T13:23:00.786Z", + "description": "Default alarm to monitor system boards. See the host's Hardware Status tab for more details.", + "entity_name": "121.0.0.0", + "name": "Host hardware system board status", + "id": "alarm-121.host-12" + }, + { + "triggered_time": "2024-09-09T13:23:00.786Z", + "description": "Default alarm to monitor storage. See the host's Hardware Status tab for more details.", + "entity_name": "121.0.0.0", + "name": "Host storage status", + "id": "alarm-124.host-12", + "status": "red" + }, + { + "entity_name": "121.0.0.0", + "name": "Host memory usage", + "id": "alarm-4.host-12", + "status": "yellow", + "triggered_time": "2024-08-28T10:31:26.621Z", + "description": "Default alarm to monitor host memory usage" + }, + { + "name": "CPU Utilization", + "id": "alarm-703.host-12", + "status": "red", + "triggered_time": "2024-08-28T10:31:26.621Z", + "description": "", + "entity_name": "121.0.0.0" + } + ], + "name": "Cluster_1", + "das_config": { + "enabled": false, + "admission": { + "control": { + "enabled": true + } + } + }, + "host": { + "count": 1, + "names": [ + "Host_1" + ] + }, + "datastore": { + "count": 1, + "names": [ + "Datastore_1" + ] + }, + "network": { + "count": 1, + "names": [ + "Network_1" + ] } - }, - "host": { - "count": 1, - "names": [ - "Host_1" - ] - }, - "datastore": { - "count": 1, - "names": [ - "Datastore_1" - ] - }, - "network": { - "count": 1, - "names": [ - "Network_1" - ] - } } - } + } } \ No newline at end of file diff --git a/metricbeat/module/vsphere/cluster/_meta/fields.yml b/metricbeat/module/vsphere/cluster/_meta/fields.yml index 7afce736285..ded927ba59e 100644 --- a/metricbeat/module/vsphere/cluster/_meta/fields.yml +++ b/metricbeat/module/vsphere/cluster/_meta/fields.yml @@ -51,4 +51,9 @@ - name: names type: keyword description: > - List of all the Network names associated with the cluster. \ No newline at end of file + List of all the Network names associated with the cluster. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. \ No newline at end of file diff --git a/metricbeat/module/vsphere/cluster/cluster.go b/metricbeat/module/vsphere/cluster/cluster.go index 5c462d84933..21d51f936d7 100644 --- a/metricbeat/module/vsphere/cluster/cluster.go +++ b/metricbeat/module/vsphere/cluster/cluster.go @@ -22,6 +22,7 @@ import ( "fmt" "strings" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" @@ -29,6 +30,7 @@ import ( "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" ) // init registers the MetricSet with the central registry as soon as the program @@ -53,6 +55,20 @@ type assetNames struct { outputHostNames []string } +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + +type metricData struct { + assetNames assetNames + triggerdAlarms []triggerdAlarm +} + // New creates a new instance of the MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { ms, err := vsphere.NewMetricSet(base) @@ -97,7 +113,7 @@ func (m *ClusterMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) er // Retrieve summary property for all Clusters var clt []mo.ClusterComputeResource - err = v.Retrieve(ctx, []string{"ClusterComputeResource"}, []string{"name", "host", "network", "datastore", "configuration"}, &clt) + err = v.Retrieve(ctx, []string{"ClusterComputeResource"}, []string{"name", "host", "network", "datastore", "configuration", "triggeredAlarmState"}, &clt) if err != nil { return fmt.Errorf("error in Retrieve: %w", err) } @@ -120,8 +136,13 @@ func (m *ClusterMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) er m.Logger().Warn("Metric das_config.enabled not found") } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, clt[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from cluster %s: %w", clt[i].Name, err) + } + reporter.Event(mb.Event{ - MetricSetFields: m.mapEvent(clt[i], assetNames), + MetricSetFields: m.mapEvent(clt[i], &metricData{assetNames: assetNames, triggerdAlarms: triggerdAlarm}), }) } } @@ -129,7 +150,7 @@ func (m *ClusterMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) er } -func getAssetNames(ctx context.Context, pc *property.Collector, cl *mo.ClusterComputeResource) (*assetNames, error) { +func getAssetNames(ctx context.Context, pc *property.Collector, cl *mo.ClusterComputeResource) (assetNames, error) { referenceList := append(cl.Datastore, cl.Host...) outputDatastoreNames := make([]string, 0, len(cl.Datastore)) @@ -137,7 +158,7 @@ func getAssetNames(ctx context.Context, pc *property.Collector, cl *mo.ClusterCo if len(referenceList) > 0 { var objects []mo.ManagedEntity if err := pc.Retrieve(ctx, referenceList, []string{"name"}, &objects); err != nil { - return nil, fmt.Errorf("failed to retrieve managed entities: %w", err) + return assetNames{}, fmt.Errorf("failed to retrieve managed entities: %w", err) } for _, ob := range objects { @@ -157,7 +178,7 @@ func getAssetNames(ctx context.Context, pc *property.Collector, cl *mo.ClusterCo if len(cl.Network) > 0 { var netObjects []mo.Network if err := pc.Retrieve(ctx, cl.Network, []string{"name"}, &netObjects); err != nil { - return nil, fmt.Errorf("failed to retrieve network objects: %w", err) + return assetNames{}, fmt.Errorf("failed to retrieve network objects: %w", err) } for _, ob := range netObjects { @@ -166,9 +187,49 @@ func getAssetNames(ctx context.Context, pc *property.Collector, cl *mo.ClusterCo } } - return &assetNames{ + return assetNames{ outputNetworkNames: outputNetworkNames, outputDatastoreNames: outputDatastoreNames, outputHostNames: outputHostNames, }, nil } + +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} diff --git a/metricbeat/module/vsphere/cluster/data.go b/metricbeat/module/vsphere/cluster/data.go index 37c2ac5c319..06cf976499b 100644 --- a/metricbeat/module/vsphere/cluster/data.go +++ b/metricbeat/module/vsphere/cluster/data.go @@ -23,23 +23,27 @@ import ( "github.com/elastic/elastic-agent-libs/mapstr" ) -func (m *ClusterMetricSet) mapEvent(cl mo.ClusterComputeResource, data *assetNames) mapstr.M { +func (m *ClusterMetricSet) mapEvent(cl mo.ClusterComputeResource, data *metricData) mapstr.M { event := mapstr.M{ "host": mapstr.M{ - "count": len(data.outputHostNames), - "names": data.outputHostNames, + "count": len(data.assetNames.outputHostNames), + "names": data.assetNames.outputHostNames, }, "datastore": mapstr.M{ - "count": len(data.outputDatastoreNames), - "names": data.outputDatastoreNames, + "count": len(data.assetNames.outputDatastoreNames), + "names": data.assetNames.outputDatastoreNames, }, "network": mapstr.M{ - "count": len(data.outputNetworkNames), - "names": data.outputNetworkNames, + "count": len(data.assetNames.outputNetworkNames), + "names": data.assetNames.outputNetworkNames, }, "name": cl.Name, } + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + if cl.Configuration.DasConfig.Enabled != nil { event.Put("das_config.enabled", *cl.Configuration.DasConfig.Enabled) } diff --git a/metricbeat/module/vsphere/cluster/data_test.go b/metricbeat/module/vsphere/cluster/data_test.go index 097b7254283..6736b8b33f7 100644 --- a/metricbeat/module/vsphere/cluster/data_test.go +++ b/metricbeat/module/vsphere/cluster/data_test.go @@ -49,7 +49,11 @@ func TestEventMapping(t *testing.T) { outputNetworkNames: []string{"Network_0"}, } - outputEvent := m.mapEvent(ClusterTest, &assetNames) + var metricDataTest = metricData{ + assetNames: assetNames, + } + + outputEvent := m.mapEvent(ClusterTest, &metricDataTest) testEvent := mapstr.M{ "das_config": mapstr.M{ "enabled": false, diff --git a/metricbeat/module/vsphere/datastore/_meta/fields.yml b/metricbeat/module/vsphere/datastore/_meta/fields.yml index e44f5cec7f2..18c4bc3846a 100644 --- a/metricbeat/module/vsphere/datastore/_meta/fields.yml +++ b/metricbeat/module/vsphere/datastore/_meta/fields.yml @@ -64,6 +64,11 @@ type: keyword description: > Status of the datastore. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. - name: vm.count type: long description: > diff --git a/metricbeat/module/vsphere/datastore/data.go b/metricbeat/module/vsphere/datastore/data.go index 413854cafa7..c4a2c5e28f5 100644 --- a/metricbeat/module/vsphere/datastore/data.go +++ b/metricbeat/module/vsphere/datastore/data.go @@ -49,6 +49,10 @@ func (m *DataStoreMetricSet) mapEvent(ds mo.Datastore, data *metricData) mapstr. }, } + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + if ds.Summary.Capacity > 0 { usedSpacePercent := float64(ds.Summary.Capacity-ds.Summary.FreeSpace) / float64(ds.Summary.Capacity) event.Put("capacity.used.pct", usedSpacePercent) diff --git a/metricbeat/module/vsphere/datastore/datastore.go b/metricbeat/module/vsphere/datastore/datastore.go index 79ab67fe978..4a0f40df93d 100644 --- a/metricbeat/module/vsphere/datastore/datastore.go +++ b/metricbeat/module/vsphere/datastore/datastore.go @@ -22,6 +22,7 @@ import ( "fmt" "strings" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" @@ -54,9 +55,19 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &DataStoreMetricSet{ms}, nil } +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + type metricData struct { - perfMetrics map[string]interface{} - assetNames assetNames + perfMetrics map[string]interface{} + assetNames assetNames + triggerdAlarms []triggerdAlarm } type assetNames struct { @@ -108,7 +119,7 @@ func (m *DataStoreMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) // Retrieve summary property for all datastores var dst []mo.Datastore - err = v.Retrieve(ctx, []string{"Datastore"}, []string{"summary", "host", "vm", "overallStatus"}, &dst) + err = v.Retrieve(ctx, []string{"Datastore"}, []string{"summary", "host", "vm", "overallStatus", "triggeredAlarmState"}, &dst) if err != nil { return fmt.Errorf("error in Retrieve: %w", err) } @@ -138,10 +149,16 @@ func (m *DataStoreMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) m.Logger().Errorf("Failed to retrieve performance metrics from datastore %s: %v", dst[i].Name, err) } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, dst[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from datastore %s: %w", dst[i].Name, err) + } + reporter.Event(mb.Event{ MetricSetFields: m.mapEvent(dst[i], &metricData{ - perfMetrics: metricMap, - assetNames: assetNames, + perfMetrics: metricMap, + triggerdAlarms: triggerdAlarm, + assetNames: assetNames, }), }) } @@ -196,6 +213,46 @@ func getAssetNames(ctx context.Context, pc *property.Collector, ds *mo.Datastore }, nil } +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} + func (m *DataStoreMetricSet) getPerfMetrics(ctx context.Context, perfManager *performance.Manager, dst mo.Datastore, metrics map[string]*types.PerfCounterInfo) (metricMap map[string]interface{}, err error) { metricMap = make(map[string]interface{}) diff --git a/metricbeat/module/vsphere/datastorecluster/_meta/fields.yml b/metricbeat/module/vsphere/datastorecluster/_meta/fields.yml index 50ce1ca9d44..43939b60691 100644 --- a/metricbeat/module/vsphere/datastorecluster/_meta/fields.yml +++ b/metricbeat/module/vsphere/datastorecluster/_meta/fields.yml @@ -25,4 +25,9 @@ - name: datastore.count type: long description: > - Number of datastores in the Datastore Cluster. \ No newline at end of file + Number of datastores in the Datastore Cluster. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. \ No newline at end of file diff --git a/metricbeat/module/vsphere/datastorecluster/data.go b/metricbeat/module/vsphere/datastorecluster/data.go index d7c7903c62c..fc727a6d01a 100644 --- a/metricbeat/module/vsphere/datastorecluster/data.go +++ b/metricbeat/module/vsphere/datastorecluster/data.go @@ -24,7 +24,7 @@ import ( ) func (m *DatastoreClusterMetricSet) mapEvent(datastoreCluster mo.StoragePod, data *metricData) mapstr.M { - return mapstr.M{ + event := mapstr.M{ "name": datastoreCluster.Name, "capacity": mapstr.M{ "bytes": datastoreCluster.Summary.Capacity, @@ -37,4 +37,10 @@ func (m *DatastoreClusterMetricSet) mapEvent(datastoreCluster mo.StoragePod, dat "count": len(data.assetNames.outputDsNames), }, } + + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + + return event } diff --git a/metricbeat/module/vsphere/datastorecluster/datastorecluster.go b/metricbeat/module/vsphere/datastorecluster/datastorecluster.go index 3cadd20a9f1..762888d06f0 100644 --- a/metricbeat/module/vsphere/datastorecluster/datastorecluster.go +++ b/metricbeat/module/vsphere/datastorecluster/datastorecluster.go @@ -26,7 +26,9 @@ import ( "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" ) @@ -58,7 +60,17 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { } type metricData struct { - assetNames assetNames + assetNames assetNames + triggerdAlarms []triggerdAlarm +} + +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` } type assetNames struct { @@ -94,7 +106,7 @@ func (m *DatastoreClusterMetricSet) Fetch(ctx context.Context, reporter mb.Repor }() var datastoreCluster []mo.StoragePod - err = v.Retrieve(ctx, []string{"StoragePod"}, []string{"name", "summary", "childEntity"}, &datastoreCluster) + err = v.Retrieve(ctx, []string{"StoragePod"}, []string{"name", "summary", "childEntity", "triggeredAlarmState"}, &datastoreCluster) if err != nil { return fmt.Errorf("error in retrieve from vsphere: %w", err) } @@ -107,10 +119,15 @@ func (m *DatastoreClusterMetricSet) Fetch(ctx context.Context, reporter mb.Repor assetNames, err := getAssetNames(ctx, pc, &datastoreCluster[i]) if err != nil { - m.Logger().Errorf("Failed to retrieve object from host %s: v", datastoreCluster[i].Name, err) + m.Logger().Errorf("Failed to retrieve object from datastore cluster %s: v", datastoreCluster[i].Name, err) } - reporter.Event(mb.Event{MetricSetFields: m.mapEvent(datastoreCluster[i], &metricData{assetNames: assetNames})}) + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, datastoreCluster[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from datastore cluster %s: %w", datastoreCluster[i].Name, err) + } + + reporter.Event(mb.Event{MetricSetFields: m.mapEvent(datastoreCluster[i], &metricData{assetNames: assetNames, triggerdAlarms: triggerdAlarm})}) } return nil @@ -136,3 +153,43 @@ func getAssetNames(ctx context.Context, pc *property.Collector, dsc *mo.StorageP outputDsNames: outputDsNames, }, nil } + +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} diff --git a/metricbeat/module/vsphere/fields.go b/metricbeat/module/vsphere/fields.go index 54f4f093b7a..09f24acb349 100644 --- a/metricbeat/module/vsphere/fields.go +++ b/metricbeat/module/vsphere/fields.go @@ -32,5 +32,5 @@ func init() { // AssetVsphere returns asset data. // This is the base64 encoded zlib format compressed contents of module/vsphere. func AssetVsphere() string { - return "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" + return "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" } diff --git a/metricbeat/module/vsphere/host/_meta/data.json b/metricbeat/module/vsphere/host/_meta/data.json index 462000c6243..26ffcbffee2 100644 --- a/metricbeat/module/vsphere/host/_meta/data.json +++ b/metricbeat/module/vsphere/host/_meta/data.json @@ -15,6 +15,40 @@ }, "vsphere": { "host": { + "triggerd_alarms": [ + { + "status": "red", + "triggered_time": "2024-09-09T13:23:00.786Z", + "description": "Default alarm to monitor system boards. See the host's Hardware Status tab for more details.", + "entity_name": "121.0.0.0", + "name": "Host hardware system board status", + "id": "alarm-121.host-12" + }, + { + "triggered_time": "2024-09-09T13:23:00.786Z", + "description": "Default alarm to monitor storage. See the host's Hardware Status tab for more details.", + "entity_name": "121.0.0.0", + "name": "Host storage status", + "id": "alarm-124.host-12", + "status": "red" + }, + { + "entity_name": "121.0.0.0", + "name": "Host memory usage", + "id": "alarm-4.host-12", + "status": "yellow", + "triggered_time": "2024-08-28T10:31:26.621Z", + "description": "Default alarm to monitor host memory usage" + }, + { + "name": "CPU Utilization", + "id": "alarm-703.host-12", + "status": "red", + "triggered_time": "2024-08-28T10:31:26.621Z", + "description": "", + "entity_name": "121.0.0.0" + } + ], "cpu": { "used": { "mhz": 67 @@ -138,4 +172,4 @@ "uptime": 1728865 } } -} \ No newline at end of file +} diff --git a/metricbeat/module/vsphere/host/_meta/fields.yml b/metricbeat/module/vsphere/host/_meta/fields.yml index b45061b863e..584a907a850 100644 --- a/metricbeat/module/vsphere/host/_meta/fields.yml +++ b/metricbeat/module/vsphere/host/_meta/fields.yml @@ -146,6 +146,11 @@ type: keyword description: > The overall health status of a host in the vSphere environment. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. - name: uptime type: long description: > diff --git a/metricbeat/module/vsphere/host/data.go b/metricbeat/module/vsphere/host/data.go index a6be1d80d07..45d9a69d2ee 100644 --- a/metricbeat/module/vsphere/host/data.go +++ b/metricbeat/module/vsphere/host/data.go @@ -46,6 +46,10 @@ func (m *HostMetricSet) mapEvent(hs mo.HostSystem, data *metricData) mapstr.M { m.Logger().Debug("'Hardware' or 'Summary' data not found. This is either a parsing error from vsphere library, an error trying to reach host/guest or incomplete information returned from host/guest") } + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + if len(data.assetNames.outputVmNames) > 0 { event.Put("vm.names", data.assetNames.outputVmNames) event.Put("vm.count", len(data.assetNames.outputVmNames)) diff --git a/metricbeat/module/vsphere/host/host.go b/metricbeat/module/vsphere/host/host.go index cd791f5d21b..6061b9145e9 100644 --- a/metricbeat/module/vsphere/host/host.go +++ b/metricbeat/module/vsphere/host/host.go @@ -22,6 +22,7 @@ import ( "fmt" "strings" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" @@ -54,9 +55,19 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &HostMetricSet{ms}, nil } +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + type metricData struct { - perfMetrics map[string]interface{} - assetNames assetNames + perfMetrics map[string]interface{} + assetNames assetNames + triggerdAlarms []triggerdAlarm } type assetNames struct { @@ -122,7 +133,7 @@ func (m *HostMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) error // Retrieve summary property for all hosts. var hst []mo.HostSystem - err = v.Retrieve(ctx, []string{"HostSystem"}, []string{"summary", "network", "name", "vm", "datastore"}, &hst) + err = v.Retrieve(ctx, []string{"HostSystem"}, []string{"summary", "network", "name", "vm", "datastore", "triggeredAlarmState"}, &hst) if err != nil { return fmt.Errorf("error in Retrieve: %w", err) } @@ -151,10 +162,16 @@ func (m *HostMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) error m.Logger().Errorf("Failed to retrieve performance metrics from host %s: %v", hst[i].Name, err) } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, hst[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve triggerd alarms from host %s: %w", hst[i].Name, err) + } + reporter.Event(mb.Event{ MetricSetFields: m.mapEvent(hst[i], &metricData{ - perfMetrics: metricMap, - assetNames: assetNames, + perfMetrics: metricMap, + triggerdAlarms: triggerdAlarm, + assetNames: assetNames, }), }) } @@ -204,6 +221,46 @@ func getAssetNames(ctx context.Context, pc *property.Collector, hs *mo.HostSyste }, nil } +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} + func (m *HostMetricSet) getPerfMetrics(ctx context.Context, perfManager *performance.Manager, hst mo.HostSystem, metrics map[string]*types.PerfCounterInfo) (metricMap map[string]interface{}, err error) { metricMap = make(map[string]interface{}) diff --git a/metricbeat/module/vsphere/network/_meta/fields.yml b/metricbeat/module/vsphere/network/_meta/fields.yml index c3c0903c34f..8cc2288ea01 100644 --- a/metricbeat/module/vsphere/network/_meta/fields.yml +++ b/metricbeat/module/vsphere/network/_meta/fields.yml @@ -46,3 +46,8 @@ type: long description: > Number of virtual machines connected to this network. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. \ No newline at end of file diff --git a/metricbeat/module/vsphere/network/data.go b/metricbeat/module/vsphere/network/data.go index 6243441cee6..d9db174db77 100644 --- a/metricbeat/module/vsphere/network/data.go +++ b/metricbeat/module/vsphere/network/data.go @@ -32,6 +32,10 @@ func (m *NetworkMetricSet) mapEvent(net mo.Network, data *metricData) mapstr.M { event.Put("config.status", net.ConfigStatus) event.Put("type", net.Self.Type) + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + if len(data.assetNames.outputHostNames) > 0 { event.Put("host.names", data.assetNames.outputHostNames) event.Put("host.count", len(data.assetNames.outputHostNames)) diff --git a/metricbeat/module/vsphere/network/network.go b/metricbeat/module/vsphere/network/network.go index 34e690009cd..d5cded4d433 100644 --- a/metricbeat/module/vsphere/network/network.go +++ b/metricbeat/module/vsphere/network/network.go @@ -26,7 +26,9 @@ import ( "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" ) @@ -56,8 +58,18 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &NetworkMetricSet{ms}, nil } +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + type metricData struct { - assetNames assetNames + assetNames assetNames + triggerdAlarms []triggerdAlarm } type assetNames struct { @@ -98,7 +110,7 @@ func (m *NetworkMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) er // Retrieve property for all networks var networks []mo.Network - err = v.Retrieve(ctx, []string{"Network"}, []string{"summary", "name", "overallStatus", "configStatus", "vm", "host"}, &networks) + err = v.Retrieve(ctx, []string{"Network"}, []string{"summary", "name", "overallStatus", "configStatus", "vm", "host", "triggeredAlarmState"}, &networks) if err != nil { return fmt.Errorf("error in Retrieve: %w", err) } @@ -115,8 +127,13 @@ func (m *NetworkMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) er continue } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, networks[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from network %s: %w", networks[i].Name, err) + } + reporter.Event(mb.Event{ - MetricSetFields: m.mapEvent(networks[i], &metricData{assetNames: assetNames}), + MetricSetFields: m.mapEvent(networks[i], &metricData{assetNames: assetNames, triggerdAlarms: triggerdAlarm}), }) } } @@ -154,3 +171,43 @@ func getAssetNames(ctx context.Context, pc *property.Collector, net *mo.Network) outputHostNames: outputHostNames, }, nil } + +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} diff --git a/metricbeat/module/vsphere/resourcepool/_meta/fields.yml b/metricbeat/module/vsphere/resourcepool/_meta/fields.yml index 8b376d34472..788a974a7fd 100644 --- a/metricbeat/module/vsphere/resourcepool/_meta/fields.yml +++ b/metricbeat/module/vsphere/resourcepool/_meta/fields.yml @@ -112,3 +112,8 @@ type: keyword description: > Names of virtual machines on the resource pool. + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. diff --git a/metricbeat/module/vsphere/resourcepool/data.go b/metricbeat/module/vsphere/resourcepool/data.go index c63c1883478..aa391a3b10c 100644 --- a/metricbeat/module/vsphere/resourcepool/data.go +++ b/metricbeat/module/vsphere/resourcepool/data.go @@ -60,5 +60,9 @@ func (m *ResourcePoolMetricSet) mapEvent(rp mo.ResourcePool, data *metricData) m event.Put("vm.count", len(data.assetNames.outputVmNames)) } + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } + return event } diff --git a/metricbeat/module/vsphere/resourcepool/resourcepool.go b/metricbeat/module/vsphere/resourcepool/resourcepool.go index 1c83e3d890d..c512f07c703 100644 --- a/metricbeat/module/vsphere/resourcepool/resourcepool.go +++ b/metricbeat/module/vsphere/resourcepool/resourcepool.go @@ -26,7 +26,9 @@ import ( "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/module/vsphere" ) @@ -58,8 +60,18 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { } // metricData holds performance metrics values. +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + type metricData struct { - assetNames assetNames + assetNames assetNames + triggerdAlarms []triggerdAlarm } type assetNames struct { @@ -102,7 +114,7 @@ func (m *ResourcePoolMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV // Retrieve property for all ResourcePools. var rps []mo.ResourcePool - err = v.Retrieve(ctx, []string{"ResourcePool"}, []string{"name", "overallStatus", "vm", "summary"}, &rps) + err = v.Retrieve(ctx, []string{"ResourcePool"}, []string{"name", "overallStatus", "vm", "summary", "triggeredAlarmState"}, &rps) if err != nil { return fmt.Errorf("error in Retrieve: %w", err) } @@ -118,8 +130,13 @@ func (m *ResourcePoolMetricSet) Fetch(ctx context.Context, reporter mb.ReporterV m.Logger().Errorf("Failed to retrieve object from resource pool %s: %v", rps[i].Name, err) } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, rps[i].TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from resource pool %s: %w", rps[i].Name, err) + } + reporter.Event(mb.Event{ - MetricSetFields: m.mapEvent(rps[i], &metricData{assetNames: assetNames}), + MetricSetFields: m.mapEvent(rps[i], &metricData{assetNames: assetNames, triggerdAlarms: triggerdAlarm}), }) } } @@ -148,3 +165,43 @@ func getAssetNames(ctx context.Context, pc *property.Collector, rp *mo.ResourceP return assetNames{outputVmNames: outputVmNames}, nil } + +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +} diff --git a/metricbeat/module/vsphere/virtualmachine/_meta/fields.yml b/metricbeat/module/vsphere/virtualmachine/_meta/fields.yml index 783ca427b34..ab6ff7600e5 100644 --- a/metricbeat/module/vsphere/virtualmachine/_meta/fields.yml +++ b/metricbeat/module/vsphere/virtualmachine/_meta/fields.yml @@ -104,5 +104,8 @@ - name: count type: long description: The number of snapshots of this virtualmachine. - - + - name: triggerd_alarms.* + type: object + object_type: keyword + description: > + List of all the triggerd alarms. diff --git a/metricbeat/module/vsphere/virtualmachine/data.go b/metricbeat/module/vsphere/virtualmachine/data.go index f7854888923..62d2866d8bb 100644 --- a/metricbeat/module/vsphere/virtualmachine/data.go +++ b/metricbeat/module/vsphere/virtualmachine/data.go @@ -74,6 +74,9 @@ func (m *MetricSet) mapEvent(data VMData) mapstr.M { event.Put("snapshot.count", len(data.Snapshots)) event.Put("snapshot.info", data.Snapshots) } + if len(data.triggerdAlarms) > 0 { + event.Put("triggerd_alarms", data.triggerdAlarms) + } return event } diff --git a/metricbeat/module/vsphere/virtualmachine/virtualmachine.go b/metricbeat/module/vsphere/virtualmachine/virtualmachine.go index 124bc0c56e5..811597b9934 100644 --- a/metricbeat/module/vsphere/virtualmachine/virtualmachine.go +++ b/metricbeat/module/vsphere/virtualmachine/virtualmachine.go @@ -50,6 +50,15 @@ type MetricSet struct { GetCustomFields bool } +type triggerdAlarm struct { + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` + TriggeredTime common.Time `json:"triggered_time"` + Description string `json:"description"` + EntityName string `json:"entity_name"` +} + type VMData struct { VM mo.VirtualMachine HostID string @@ -58,6 +67,7 @@ type VMData struct { DatastoreNames []string CustomFields mapstr.M Snapshots []VMSnapshotData + triggerdAlarms []triggerdAlarm } type VMSnapshotData struct { @@ -136,7 +146,7 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) error { // Retrieve summary property for all machines var vmt []mo.VirtualMachine - err = v.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary", "datastore", "snapshot"}, &vmt) + err = v.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary", "datastore", "triggeredAlarmState", "snapshot"}, &vmt) if err != nil { return fmt.Errorf("virtualmachine: error in Retrieve: %w", err) } @@ -194,6 +204,11 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) error { snapshots = fetchSnapshots(vm.Snapshot.RootSnapshotList) } + triggerdAlarm, err := getTriggerdAlarm(ctx, pc, vm.TriggeredAlarmState) + if err != nil { + m.Logger().Errorf("Failed to retrieve alerts from VM %s: %w", vm.Name, err) + } + data := VMData{ VM: vm, HostID: hostID, @@ -202,6 +217,7 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) error { DatastoreNames: datastoreNames, CustomFields: customFields, Snapshots: snapshots, + triggerdAlarms: triggerdAlarm, } reporter.Event(mb.Event{ @@ -305,3 +321,43 @@ func fetchSnapshots(snapshotTree []types.VirtualMachineSnapshotTree) []VMSnapsho } return snapshots } + +func getTriggerdAlarm(ctx context.Context, pc *property.Collector, triggeredAlarmState []types.AlarmState) ([]triggerdAlarm, error) { + var triggeredAlarms []triggerdAlarm + for _, alarmState := range triggeredAlarmState { + var triggeredAlarm triggerdAlarm + var alarm mo.Alarm + err := pc.RetrieveOne(ctx, alarmState.Alarm, nil, &alarm) + if err != nil { + return nil, err + } + triggeredAlarm.Name = alarm.Info.Name + + var entityName string + if alarmState.Entity.Type == "Network" { + var entity mo.Network + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } else { + var entity mo.ManagedEntity + if err := pc.RetrieveOne(ctx, alarmState.Entity, []string{"name"}, &entity); err != nil { + return nil, err + } + + entityName = entity.Name + } + triggeredAlarm.EntityName = entityName + + triggeredAlarm.Description = alarm.Info.Description + triggeredAlarm.ID = alarmState.Key + triggeredAlarm.Status = string(alarmState.OverallStatus) + triggeredAlarm.TriggeredTime = common.Time(alarmState.Time) + + triggeredAlarms = append(triggeredAlarms, triggeredAlarm) + } + + return triggeredAlarms, nil +}