Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions cmd/collectors/rest/plugins/license/license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package license

import (
"log/slog"

"github.com/netapp/harvest/v2/cmd/collectors"
"github.com/netapp/harvest/v2/cmd/poller/plugin"
"github.com/netapp/harvest/v2/pkg/collector"
"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/third_party/tidwall/gjson"
)

const licenseMatrix = "license"

type License struct {
*plugin.AbstractPlugin
data *matrix.Matrix
}

func New(p *plugin.AbstractPlugin) plugin.Plugin {
return &License{AbstractPlugin: p}
}

func (l *License) Init(_ conf.Remote) error {
if err := l.InitAbc(); err != nil {
return err
}

l.data = matrix.New(l.Parent+".License", licenseMatrix, licenseMatrix)

exportOptions := node.NewS("export_options")
instanceKeys := exportOptions.NewChildS("instance_keys", "")
for _, k := range []string{"license", "scope", "owner"} {
instanceKeys.NewChildS("", k)
}
instanceLabels := exportOptions.NewChildS("instance_labels", "")
for _, il := range []string{
"description", "entitlement_action", "entitlement_risk",
"serial_number", "installed_license", "host_id",
"active", "evaluation", "compliance_state",
Comment thread
rahulguptajss marked this conversation as resolved.
} {
instanceLabels.NewChildS("", il)
}
l.data.SetExportOptions(exportOptions)

for _, m := range []string{"capacity_maximum_size", "capacity_used_size", "capacity_used_percent", "expiry_time"} {
if _, err := l.data.NewMetricFloat64(m, m); err != nil {
l.SLogger.Error("add metric", slogx.Err(err), slog.String("metric", m))
return err
}
}

return nil
}

func (l *License) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *collector.Metadata, error) {
data := dataMap[l.Object]

l.data.PurgeInstances()
l.data.Reset()
l.data.SetGlobalLabels(data.GetGlobalLabels())

for _, instance := range data.GetInstances() {
licenseName := instance.GetLabel("license")
scope := instance.GetLabel("scope")
description := instance.GetLabel("description")
entitlementAction := instance.GetLabel("entitlement_action")
entitlementRisk := instance.GetLabel("entitlement_risk")

rawLicenses := instance.GetLabel("licenses")
if rawLicenses == "" {
continue
}

licensesData := gjson.Result{Type: gjson.JSON, Raw: "[" + rawLicenses + "]"}
for _, lic := range licensesData.Array() {
owner := lic.Get("owner").ClonedString()
instanceKey := licenseName + "_" + scope + "_" + owner

newInstance, err := l.data.NewInstance(instanceKey)
if err != nil {
l.SLogger.Error("Failed to create instance", slogx.Err(err), slog.String("key", instanceKey))
continue
}

newInstance.SetLabel("license", licenseName)
newInstance.SetLabel("scope", scope)
newInstance.SetLabel("description", description)
newInstance.SetLabel("entitlement_action", entitlementAction)
newInstance.SetLabel("entitlement_risk", entitlementRisk)

newInstance.SetLabel("owner", owner)
newInstance.SetLabel("serial_number", lic.Get("serial_number").ClonedString())
newInstance.SetLabel("installed_license", lic.Get("installed_license").ClonedString())
newInstance.SetLabel("host_id", lic.Get("host_id").ClonedString())
newInstance.SetLabel("active", lic.Get("active").ClonedString())
newInstance.SetLabel("evaluation", lic.Get("evaluation").ClonedString())
newInstance.SetLabel("compliance_state", lic.Get("compliance.state").ClonedString())

if expiryStr := lic.Get("expiry_time").ClonedString(); expiryStr != "" {
if m := l.data.GetMetric("expiry_time"); m != nil {
m.SetValueFloat64(newInstance, collectors.HandleTimestamp(expiryStr)*1000)
}
}
if lic.Get("capacity").Exists() {
maxSize := lic.Get("capacity.maximum_size").Float()
usedSize := lic.Get("capacity.used_size").Float()
if m := l.data.GetMetric("capacity_maximum_size"); m != nil {
m.SetValueFloat64(newInstance, maxSize)
}
if m := l.data.GetMetric("capacity_used_size"); m != nil {
m.SetValueFloat64(newInstance, usedSize)
}
if m := l.data.GetMetric("capacity_used_percent"); m != nil && maxSize > 0 {
m.SetValueFloat64(newInstance, usedSize/maxSize*100)
}
}
}
}
return []*matrix.Matrix{l.data}, nil, nil
}
Comment thread
rahulguptajss marked this conversation as resolved.
3 changes: 3 additions & 0 deletions cmd/collectors/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"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/igroup"
"github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/license"
"github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/mav"
"github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/metroclustercheck"
"github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/netroute"
Expand Down Expand Up @@ -559,6 +560,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin
return collectors.NewStorageUnit(abc)
case "Igroup":
return igroup.New(abc)
case "License":
return license.New(abc)
default:
r.Logger.Warn("no rest plugin found", slog.String("kind", kind))
}
Expand Down
40 changes: 40 additions & 0 deletions cmd/tools/generate/counter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,46 @@ counters:
- Name: flexcache_miss_percent
Description: This metric represents the percentage of block requests from a client that resulted in a "miss" in the FlexCache. A "miss" occurs when the requested data is not found in the cache and has to be retrieved from the origin volume.

- Name: license_labels
Description: Detailed information about licenses installed on the cluster.
APIs:
- API: REST
Endpoint: api/cluster/licensing/licenses
ONTAPCounter: Harvest generated
Template: conf/rest/9.12.0/license.yaml

- Name: license_capacity_maximum_size
Description: Maximum licensed capacity in bytes for capacity-based licenses. This metric is only applicable to licenses that have capacity limits.
APIs:
- API: REST
Endpoint: api/cluster/licensing/licenses
ONTAPCounter: Harvest generated
Template: conf/rest/9.12.0/license.yaml

- Name: license_capacity_used_size
Description: The amount of capacity currently being used against the license limit in bytes. This metric is only applicable to capacity-based licenses.
APIs:
- API: REST
Endpoint: api/cluster/licensing/licenses
ONTAPCounter: Harvest generated
Template: conf/rest/9.12.0/license.yaml

- Name: license_capacity_used_percent
Description: The percentage of the licensed capacity currently being used. This is calculated as (capacity_used_size / capacity_maximum_size) * 100 and is only applicable to capacity-based licenses.
APIs:
- API: REST
Endpoint: api/cluster/licensing/licenses
ONTAPCounter: Harvest generated
Template: conf/rest/9.12.0/license.yaml

- Name: license_expiry_time
Description: License expiration timestamp in Unix epoch milliseconds. This metric indicates when the license will expire. For perpetual licenses, this metric will not be present.
APIs:
- API: REST
Endpoint: api/cluster/licensing/licenses
ONTAPCounter: Harvest generated
Template: conf/rest/9.12.0/license.yaml
Comment thread
rahulguptajss marked this conversation as resolved.

- Name: lun_size_used_percent
Description: This metric represents the percentage of a LUN that is currently being used.

Expand Down
16 changes: 16 additions & 0 deletions conf/rest/9.12.0/license.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: License
query: api/cluster/licensing/licenses
object: license

counters:
- ^^name => license
- ^description
- ^entitlement.action => entitlement_action
- ^entitlement.risk => entitlement_risk
- ^licenses
- ^scope

plugins:
- License

export_data: false
1 change: 1 addition & 0 deletions conf/rest/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ objects:
FlexCache: flexcache.yaml
FCP: fcp.yaml
FRU: fru.yaml
License: license.yaml
LIF: lif.yaml
# Lock: lock.yaml
Health: health.yaml
Expand Down
11 changes: 10 additions & 1 deletion docs/cisco-switch-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See
[#1577](https://github.com/NetApp/harvest/issues/1577#issue-1471478260) for details.

```
Creation Date : 2026-Feb-23
Creation Date : 2026-Mar-02
NX-OS Version: 9.3.12
```

Expand Down Expand Up @@ -525,6 +525,15 @@ Displays operational state of the interface in the Cisco switch.
|--------|----------|--------|---------|
| NXAPI | `show interface` | `state` | conf/ciscorest/nxos/9.3.12/interface.yaml |

The `cisco_interface_up` metric is visualized in the following Grafana dashboards:

/// html | div.grafana-table
| Dashboard | Row | Type | Panel |
|--------|----------|--------|--------|
| Cisco: Switch | Interfaces | table | [Down (Last 24h)](/d/cisco-switch/cisco3a-switch?orgId=1&viewPanel=39) |
///



### cisco_lldp_neighbor_labels

Expand Down
2 changes: 1 addition & 1 deletion docs/eseries-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ These can be generated on demand by running `bin/harvest grafana metrics`. See
[#1577](https://github.com/NetApp/harvest/issues/1577#issue-1471478260) for details.

```
Creation Date : 2026-Feb-20
Creation Date : 2026-Mar-02
E-Series Version: 11.80.0
```

Expand Down
Loading
Loading