diff --git a/cmd/collectors/rest/plugins/clustersoftware/clustersoftware.go b/cmd/collectors/rest/plugins/clustersoftware/clustersoftware.go new file mode 100644 index 000000000..54c9a0d61 --- /dev/null +++ b/cmd/collectors/rest/plugins/clustersoftware/clustersoftware.go @@ -0,0 +1,255 @@ +package clustersoftware + +import ( + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/pkg/conf" + "github.com/netapp/harvest/v2/pkg/matrix" + "github.com/netapp/harvest/v2/pkg/slogx" + "github.com/netapp/harvest/v2/pkg/tree/node" + "github.com/netapp/harvest/v2/pkg/util" + "github.com/tidwall/gjson" + "log/slog" +) + +const updateMatrix = "cluster_software_update" +const StatusMatrix = "cluster_software_status" +const validationMatrix = "cluster_software_validation" +const labels = "labels" + +type ClusterSoftware struct { + *plugin.AbstractPlugin + data map[string]*matrix.Matrix +} + +func New(p *plugin.AbstractPlugin) plugin.Plugin { + return &ClusterSoftware{AbstractPlugin: p} +} + +func (c *ClusterSoftware) Init(conf.Remote) error { + if err := c.InitAbc(); err != nil { + return err + } + + c.data = make(map[string]*matrix.Matrix) + if err := c.createUpdateMetrics(); err != nil { + return err + } + if err := c.createStatusMetrics(); err != nil { + return err + } + if err := c.createValidationMetrics(); err != nil { + return err + } + + return nil +} + +func (c *ClusterSoftware) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { + data := dataMap[c.Object] + globalLabels := data.GetGlobalLabels() + + for _, instance := range data.GetInstances() { + instance.SetExportable(false) + // generate update details metrics + updateDetails := instance.GetLabel("update_details") + updateDetailsJSON := gjson.Result{Type: gjson.JSON, Raw: "[" + updateDetails + "]"} + c.handleUpdateDetails(updateDetailsJSON, globalLabels) + + // generate status details metrics + statusDetails := instance.GetLabel("status_details") + statusDetailsJSON := gjson.Result{Type: gjson.JSON, Raw: "[" + statusDetails + "]"} + c.handleStatusDetails(statusDetailsJSON, globalLabels) + + // generate update details metrics + validationResults := instance.GetLabel("validation_results") + validationResultsJSON := gjson.Result{Type: gjson.JSON, Raw: "[" + validationResults + "]"} + c.handleValidationDetails(validationResultsJSON, globalLabels) + } + + softwareMetrics := make([]*matrix.Matrix, 0, len(c.data)) + for _, val := range c.data { + softwareMetrics = append(softwareMetrics, val) + } + + return softwareMetrics, nil, nil +} + +func (c *ClusterSoftware) createUpdateMetrics() error { + mat := matrix.New(c.Parent+".ClusterSoftware", updateMatrix, updateMatrix) + exportOptions := node.NewS("export_options") + instanceKeys := exportOptions.NewChildS("instance_keys", "") + instanceKeys.NewChildS("", "phase") + instanceKeys.NewChildS("", "state") + instanceKeys.NewChildS("", "node") + + mat.SetExportOptions(exportOptions) + + if _, err := mat.NewMetricFloat64(labels, labels); err != nil { + c.SLogger.Error("Failed to create metric", slogx.Err(err), slog.String("metric", labels)) + return err + } + + c.data[updateMatrix] = mat + return nil +} + +func (c *ClusterSoftware) createStatusMetrics() error { + mat := matrix.New(c.Parent+".ClusterUpdate", StatusMatrix, StatusMatrix) + exportOptions := node.NewS("export_options") + instanceKeys := exportOptions.NewChildS("instance_keys", "") + instanceKeys.NewChildS("", "state") + instanceKeys.NewChildS("", "node") + instanceKeys.NewChildS("", "name") + + mat.SetExportOptions(exportOptions) + + if _, err := mat.NewMetricFloat64(labels, labels); err != nil { + c.SLogger.Error("Failed to create metric", slogx.Err(err), slog.String("metric", labels)) + return err + } + + c.data[StatusMatrix] = mat + return nil +} + +func (c *ClusterSoftware) createValidationMetrics() error { + mat := matrix.New(c.Parent+".ClusterUpdate", validationMatrix, validationMatrix) + exportOptions := node.NewS("export_options") + instanceKeys := exportOptions.NewChildS("instance_keys", "") + instanceKeys.NewChildS("", "status") + instanceKeys.NewChildS("", "update_check") + + mat.SetExportOptions(exportOptions) + + if _, err := mat.NewMetricFloat64(labels, labels); err != nil { + c.SLogger.Error("Failed to create metric", slogx.Err(err), slog.String("metric", labels)) + return err + } + + c.data[validationMatrix] = mat + return nil +} + +func (c *ClusterSoftware) handleUpdateDetails(updateDetailsJSON gjson.Result, globalLabels map[string]string) { + var ( + clusterUpdateInstance *matrix.Instance + key string + err error + ) + // Purge and reset data + c.data[updateMatrix].PurgeInstances() + c.data[updateMatrix].Reset() + + // Set all global labels + c.data[updateMatrix].SetGlobalLabels(globalLabels) + + for _, updateDetail := range updateDetailsJSON.Array() { + phase := updateDetail.Get("phase").String() + state := updateDetail.Get("state").String() + nodeName := updateDetail.Get("node.name").String() + key = phase + state + nodeName + + if clusterUpdateInstance, err = c.data[updateMatrix].NewInstance(key); err != nil { + c.SLogger.Error("Failed to create instance", slogx.Err(err), slog.String("key", key)) + continue + } + clusterUpdateInstance.SetLabel("node", nodeName) + clusterUpdateInstance.SetLabel("state", state) + clusterUpdateInstance.SetLabel("phase", phase) + + // populate numeric data + value := 0.0 + if state == "completed" { + value = 1.0 + } + + met := c.data[updateMatrix].GetMetric(labels) + if err := met.SetValueFloat64(clusterUpdateInstance, value); err != nil { + c.SLogger.Error("Failed to parse value", slogx.Err(err), slog.Float64("value", value)) + } else { + c.SLogger.Debug("added value", slog.Float64("value", value)) + } + } +} + +func (c *ClusterSoftware) handleStatusDetails(statusDetailsJSON gjson.Result, globalLabels map[string]string) { + var ( + clusterStatusInstance *matrix.Instance + key string + err error + ) + // Purge and reset data + c.data[StatusMatrix].PurgeInstances() + c.data[StatusMatrix].Reset() + + // Set all global labels + c.data[StatusMatrix].SetGlobalLabels(globalLabels) + + for _, updateDetail := range statusDetailsJSON.Array() { + name := updateDetail.Get("name").String() + state := updateDetail.Get("state").String() + nodeName := updateDetail.Get("node.name").String() + key = name + state + nodeName + + if clusterStatusInstance, err = c.data[StatusMatrix].NewInstance(key); err != nil { + c.SLogger.Error("Failed to create instance", slogx.Err(err), slog.String("key", key)) + continue + } + clusterStatusInstance.SetLabel("node", nodeName) + clusterStatusInstance.SetLabel("state", state) + clusterStatusInstance.SetLabel("name", name) + + // populate numeric data + value := 0.0 + if state == "completed" { + value = 1.0 + } + + met := c.data[StatusMatrix].GetMetric(labels) + if err := met.SetValueFloat64(clusterStatusInstance, value); err != nil { + c.SLogger.Error("Failed to parse value", slogx.Err(err), slog.Float64("value", value)) + } else { + c.SLogger.Debug("added value", slog.Float64("value", value)) + } + } +} + +func (c *ClusterSoftware) handleValidationDetails(validationDetailsJSON gjson.Result, globalLabels map[string]string) { + var ( + clusterValidationInstance *matrix.Instance + key string + err error + ) + // Purge and reset data + c.data[validationMatrix].PurgeInstances() + c.data[validationMatrix].Reset() + + // Set all global labels + c.data[validationMatrix].SetGlobalLabels(globalLabels) + + for _, updateDetail := range validationDetailsJSON.Array() { + updateCheck := updateDetail.Get("update_check").String() + status := updateDetail.Get("status").String() + key = updateCheck + status + + if clusterValidationInstance, err = c.data[validationMatrix].NewInstance(key); err != nil { + c.SLogger.Error("Failed to create instance", slogx.Err(err), slog.String("key", key)) + continue + } + clusterValidationInstance.SetLabel("update_check", updateCheck) + clusterValidationInstance.SetLabel("status", status) + + // populate numeric data + value := 0.0 + if status == "warning" { + value = 1.0 + } + + met := c.data[validationMatrix].GetMetric(labels) + if err := met.SetValueFloat64(clusterValidationInstance, value); err != nil { + c.SLogger.Error("Failed to parse value", slogx.Err(err), slog.Float64("value", value)) + } else { + c.SLogger.Debug("added value", slog.Float64("value", value)) + } + } +} diff --git a/cmd/collectors/rest/plugins/clusterupdate/clusterupdate.go b/cmd/collectors/rest/plugins/clusterupdate/clusterupdate.go deleted file mode 100644 index b002ccced..000000000 --- a/cmd/collectors/rest/plugins/clusterupdate/clusterupdate.go +++ /dev/null @@ -1,92 +0,0 @@ -package clusterupdate - -import ( - "github.com/netapp/harvest/v2/cmd/poller/plugin" - "github.com/netapp/harvest/v2/pkg/conf" - "github.com/netapp/harvest/v2/pkg/matrix" - "github.com/netapp/harvest/v2/pkg/slogx" - "github.com/netapp/harvest/v2/pkg/tree/node" - "github.com/netapp/harvest/v2/pkg/util" - "github.com/tidwall/gjson" - "log/slog" -) - -type ClusterUpdate struct { - *plugin.AbstractPlugin - data *matrix.Matrix -} - -func New(p *plugin.AbstractPlugin) plugin.Plugin { - return &ClusterUpdate{AbstractPlugin: p} -} - -func (c *ClusterUpdate) Init(conf.Remote) error { - if err := c.InitAbc(); err != nil { - return err - } - - c.data = matrix.New(c.Parent+".ClusterUpdate", "cluster_update", "cluster_update") - exportOptions := node.NewS("export_options") - instanceKeys := exportOptions.NewChildS("instance_keys", "") - instanceKeys.NewChildS("", "phase") - instanceKeys.NewChildS("", "state") - instanceKeys.NewChildS("", "node") - c.data.SetExportOptions(exportOptions) - - if _, err := c.data.NewMetricFloat64("status", "status"); err != nil { - c.SLogger.Error("Failed to create metric", slogx.Err(err), slog.String("metric", "status")) - return err - } - - return nil -} - -func (c *ClusterUpdate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { - var ( - clusterUpdateInstance *matrix.Instance - key string - err error - ) - // Purge and reset data - c.data.PurgeInstances() - c.data.Reset() - - // Set all global labels - data := dataMap[c.Object] - c.data.SetGlobalLabels(data.GetGlobalLabels()) - - for _, instance := range data.GetInstances() { - instance.SetExportable(false) - updateDetails := instance.GetLabel("update_details") - updateDetailsJSON := gjson.Result{Type: gjson.JSON, Raw: "[" + updateDetails + "]"} - for _, updateDetail := range updateDetailsJSON.Array() { - phase := updateDetail.Get("phase").String() - state := updateDetail.Get("state").String() - nodeName := updateDetail.Get("node.name").String() - key = phase + state + nodeName - - if clusterUpdateInstance, err = c.data.NewInstance(key); err != nil { - c.SLogger.Error("Failed to create instance", slogx.Err(err), slog.String("key", key)) - continue - } - clusterUpdateInstance.SetLabel("node", nodeName) - clusterUpdateInstance.SetLabel("state", state) - clusterUpdateInstance.SetLabel("phase", phase) - - // populate numeric data - value := 0.0 - if state == "completed" { - value = 1.0 - } - - met := c.data.GetMetric("status") - if err := met.SetValueFloat64(clusterUpdateInstance, value); err != nil { - c.SLogger.Error("Failed to parse value", slogx.Err(err), slog.Float64("value", value)) - } else { - c.SLogger.Debug("added value", slog.Float64("value", value)) - } - } - } - - return []*matrix.Matrix{c.data}, nil, nil -} diff --git a/cmd/collectors/rest/rest.go b/cmd/collectors/rest/rest.go index de92ae737..afab98929 100644 --- a/cmd/collectors/rest/rest.go +++ b/cmd/collectors/rest/rest.go @@ -7,7 +7,7 @@ import ( "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/aggregate" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/certificate" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/cluster" - "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/clusterupdate" + "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/clustersoftware" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/disk" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/health" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/metroclustercheck" @@ -488,8 +488,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return aggregate.New(abc) case "Cluster": return cluster.New(abc) - case "ClusterUpdate": - return clusterupdate.New(abc) + case "ClusterSoftware": + return clustersoftware.New(abc) case "Disk": return disk.New(abc) case "Health": diff --git a/conf/rest/9.6.0/clustersoftware.yaml b/conf/rest/9.6.0/clustersoftware.yaml new file mode 100644 index 000000000..6760466d5 --- /dev/null +++ b/conf/rest/9.6.0/clustersoftware.yaml @@ -0,0 +1,14 @@ + +name: ClusterSoftware +query: api/cluster/software +object: cluster_software + +counters: + - ^status_details => status_details + - ^update_details => update_details + - ^validation_results => validation_results + +plugins: + - ClusterSoftware + + diff --git a/conf/rest/9.6.0/clusterupdate.yaml b/conf/rest/9.6.0/clusterupdate.yaml deleted file mode 100644 index 386e27351..000000000 --- a/conf/rest/9.6.0/clusterupdate.yaml +++ /dev/null @@ -1,12 +0,0 @@ - -name: ClusterUpdate -query: api/cluster/software -object: cluster_update - -counters: - - ^update_details => update_details - -plugins: - - ClusterUpdate - - diff --git a/conf/rest/default.yaml b/conf/rest/default.yaml index 27b3c31a1..2fa210fd6 100644 --- a/conf/rest/default.yaml +++ b/conf/rest/default.yaml @@ -16,7 +16,7 @@ objects: # CIFSShare: cifs_share.yaml CloudTarget: cloud_target.yaml ClusterPeer: clusterpeer.yaml - ClusterUpdate: clusterupdate.yaml + ClusterSoftware: clustersoftware.yaml Disk: disk.yaml EmsDestination: ems_destination.yaml # ExportRule: exports.yaml