Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrape license information #214

Merged
merged 10 commits into from
Jul 5, 2023
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
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Please have a look on the new SSH related parameters and update your service uni
## Features
The following metrics are supported by now:
* Interfaces (bytes transmitted/received, errors, drops, speed)
* Interface L1/L2 details (FEC, MAC statistics)
* L2 security (BPDU-block violations)
* Routes (per table, by protocol)
* Alarms (count)
* BGP (message count, prefix counts per peer, session state)
Expand All @@ -43,9 +45,18 @@ The following metrics are supported by now:
* Storage (total, available and used blocks, used percentage)
* Firewall filters (counters and policers) - needs explicit rights beyond read-only
* Security policy (SRX) statistics
* Statistics about l2circuits (tunnel state, number of tunnels)
* Interface queue statistics
* Power (Power usage)
* License statistics (installed/used/needed)
* L2circuits (tunnel state, number of tunnels)
* LDP (number of neighbors, sessions and session states)
* VRRP (state per interface)


## Feature specific mappings
Some collected time series behave like enums - Integer values represent a certain state/meaning.

### L2circuits
```
0:EI -- encapsulation invalid
1:MM -- mtu mismatch
Expand All @@ -71,14 +82,14 @@ The following metrics are supported by now:
21:RS -- remote site standby
22:HS -- Hot-standby Connection
```
* LDP (number of neighbors, sessions and session states)
States map to human readable names like this:

### LDP
```
0: "Nonexistant"
1: "Operational"
```
* RPKI Session Information
States map to human readable names like this:

### RPKI
```
0 = "Down"
1 = "Up"
Expand All @@ -87,14 +98,24 @@ States map to human readable names like this:
4 = "Ex-Incr"
5 = "Ex-Full"
```
* VRRP (state per interface)

### VRRP
States map to human readable names like this:
```
1: "init"
2: "backup"
3: "master"
```

### License statistics
Expiry is either presented as number of days until expiry date or certain special values.
```
0 ... n = Days until expiry
-1 = Expired
+Inf = Permanent license
-Inf = Invalid
```

## Install
```bash
go get -u github.com/czerwonk/junos_exporter@master
Expand Down
2 changes: 1 addition & 1 deletion collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device) {
c.addCollectorIfEnabledForDevice(device, "security", f.Security, security.NewCollector)
c.addCollectorIfEnabledForDevice(device, "security_policies", f.SecurityPolicies, securitypolicies.NewCollector)
c.addCollectorIfEnabledForDevice(device, "storage", f.Storage, storage.NewCollector)
c.addCollectorIfEnabledForDevice(device, "system", f.System, system.NewCollector)
c.addCollectorIfEnabledForDevice(device, "system", (f.System || f.License), system.NewCollector)
c.addCollectorIfEnabledForDevice(device, "power", f.Power, power.NewCollector)
c.addCollectorIfEnabledForDevice(device, "mac", f.MAC, mac.NewCollector)
c.addCollectorIfEnabledForDevice(device, "vrrp", f.VRRP, vrrp.NewCollector)
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type FeatureConfig struct {
MPLSLSP bool `yaml:"mpls_lsp,omitempty"`
VPWS bool `yaml:"vpws,omitempty"`
VRRP bool `yaml:"vrrp,omitempty"`
License bool `yaml:"license,omitempty"`
}

// New creates a new config
Expand Down Expand Up @@ -136,6 +137,7 @@ func setDefaultValues(c *Config) {
f.VPWS = false
f.VRRP = false
f.BFD = false
f.License = false
}

// FeaturesForDevice gets the feature set configured for a device
Expand Down
4 changes: 4 additions & 0 deletions junos_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func clientForDevice(device *connector.Device, connManager *connector.SSHConnect
opts = append(opts, rpc.WithSatellite())
}

if cfg.Features.License {
opts = append(opts, rpc.WithLicenseInformation())
}

c := rpc.NewClient(conn, opts...)
return c, nil
}
Expand Down
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ var (
macEnabled = flag.Bool("mac.enabled", false, "Scrape MAC address table metrics")
alarmFilter = flag.String("alarms.filter", "", "Regex to filter for alerts to ignore")
configFile = flag.String("config.file", "", "Path to config file")
dynamicIfaceLabels = flag.Bool("dynamic-interface-labels", true, "Parse interface descriptions to get labels dynamicly")
dynamicIfaceLabels = flag.Bool("dynamic-interface-labels", true, "Parse interface descriptions to get labels dynamically")
interfaceDescriptionRegex = flag.String("interface-description-regex", "", "give a regex to retrieve the interface description labels")
lsEnabled = flag.Bool("logical-systems.enabled", false, "Enable logical systems support")
powerEnabled = flag.Bool("power.enabled", true, "Scrape power metrics")
lacpEnabled = flag.Bool("lacp.enabled", false, "Scrape LACP metrics")
bfdEnabled = flag.Bool("bfd.enabled", false, "Scrape BFD metrics")
vpwsEnabled = flag.Bool("vpws.enabled", false, "Scrape EVPN VPWS metrics")
mplsLSPEnabled = flag.Bool("mpls_lsp.enabled", false, "Scrape MPLS LSP metrics")
licenseEnabled = flag.Bool("license.enabled", false, "Scrape license metrics")
tlsEnabled = flag.Bool("tls.enabled", false, "Enables TLS")
tlsCertChainPath = flag.String("tls.cert-file", "", "Path to TLS cert file")
tlsKeyPath = flag.String("tls.key-file", "", "Path to TLS key file")
Expand Down Expand Up @@ -242,6 +243,7 @@ func loadConfigFromFlags() *config.Config {
f.BFD = *bfdEnabled
f.VPWS = *vpwsEnabled
f.MPLSLSP = *mplsLSPEnabled
f.License = *licenseEnabled

return c
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/collector/rpc_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Client interface {

// IsSatelliteEnabled returns if sattelite features are enabled on the device
IsSatelliteEnabled() bool

IsScrapingLicenseEnabled() bool

// Device returns device information for the connected device
Device() *connector.Device
Expand Down
56 changes: 56 additions & 0 deletions pkg/features/system/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"regexp"
"strconv"
"strings"
"time"
"math"

"github.com/czerwonk/junos_exporter/pkg/collector"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -47,6 +49,11 @@ var (

hardwareInfoDesc *prometheus.Desc

licenseUsedDesc *prometheus.Desc
licenseInstalledDesc *prometheus.Desc
licenseNeededDesc *prometheus.Desc
licenseExpiryDesc *prometheus.Desc

// regex
regex1Ints *regexp.Regexp = regexp.MustCompile(`^(\d+).*`)
regex2Ints *regexp.Regexp = regexp.MustCompile(`^(\d+)\/(\d+).*`)
Expand Down Expand Up @@ -96,6 +103,13 @@ func init() {

l = append(l, "model", "os", "os_version", "serial", "hostname", "alias", "slot_id", "state")
hardwareInfoDesc = prometheus.NewDesc(prefix+"hardware_info", "Hardware information about this system", l, nil)

l = []string{"target"}
l = append(l, "feature_name", "feature_description")
licenseUsedDesc = prometheus.NewDesc(prefix+"license_used", "Amount of license used", l, nil)
licenseInstalledDesc = prometheus.NewDesc(prefix+"license_installed", "Amount of license installed", l, nil)
licenseNeededDesc = prometheus.NewDesc(prefix+"license_needed", "Amount of license needed", l, nil)
licenseExpiryDesc = prometheus.NewDesc(prefix+"license_expiry", "Days until expiry, if applicable; -1 = expired; +Inf = permanent; -Inf = invalid", l, nil)
}

// NewCollector creates a new collector
Expand Down Expand Up @@ -133,6 +147,10 @@ func (*systemCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- sfbufsDelayedDesc
ch <- ioInitDesc
ch <- hardwareInfoDesc
ch <- licenseUsedDesc
ch <- licenseInstalledDesc
ch <- licenseNeededDesc
ch <- licenseExpiryDesc
}

// Collect collects metrics from JunOS
Expand Down Expand Up @@ -160,6 +178,10 @@ func (c *systemCollector) CollectSystem(client collector.Client, ch chan<- prome
c.collectSatelites(client, ch, labelValues)
}

if client.IsScrapingLicenseEnabled() {
c.collectLicense(client, ch, labelValues)
}

return nil
}

Expand Down Expand Up @@ -370,3 +392,37 @@ func (c *systemCollector) collectSatelites(client collector.Client, ch chan<- pr
ch <- prometheus.MustNewConstMetric(hardwareInfoDesc, prometheus.GaugeValue, float64(1), l...)
}
}

func (c *systemCollector) collectLicense(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) {
r := &licenseInformation{}
err := client.RunCommandAndParse("show system license usage", r)

if err != nil {
return
}
for _, lic := range r.LicenseInfo.License {
licenseLabels := append(labelValues,
strings.ToLower(lic.Name),
strings.ToLower(lic.Description))

ch <- prometheus.MustNewConstMetric(licenseUsedDesc, prometheus.GaugeValue, float64(lic.Used), licenseLabels...)
ch <- prometheus.MustNewConstMetric(licenseInstalledDesc, prometheus.GaugeValue, float64(lic.Installed), licenseLabels...)
ch <- prometheus.MustNewConstMetric(licenseNeededDesc, prometheus.GaugeValue, float64(lic.Needed), licenseLabels...)

expiry_str := strings.ToLower(lic.ValidityType)
expiry, err := time.Parse("2006-01-02 03:04:05 MST", expiry_str)
if err != nil {
if strings.Compare(expiry_str, "expired") == 0 {
ch <- prometheus.MustNewConstMetric(licenseExpiryDesc, prometheus.GaugeValue, float64(-1), licenseLabels...)
} else if strings.Compare(expiry_str, "permanent") == 0 {
ch <- prometheus.MustNewConstMetric(licenseExpiryDesc, prometheus.GaugeValue, float64(math.Inf(1)), licenseLabels...)
} else {
ch <- prometheus.MustNewConstMetric(licenseExpiryDesc, prometheus.GaugeValue, float64(math.Inf(-1)), licenseLabels...)
}
} else {
license_ttl_days := time.Until(expiry).Hours() / 24.0
ch <- prometheus.MustNewConstMetric(licenseExpiryDesc, prometheus.GaugeValue, float64(license_ttl_days), licenseLabels...)
}
}
}

13 changes: 13 additions & 0 deletions pkg/features/system/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,16 @@ type satelliteChassis struct {
} `xml:"satellite"`
} `xml:"satellite-information"`
}

type licenseInformation struct {
LicenseInfo struct {
License []struct {
Name string `xml:"name"`
Description string `xml:"description"`
Installed int `xml:"licensed"`
Used int `xml:"used-licensed"`
Needed int `xml:"needed"`
ValidityType string `xml:"validity-type"`
} `xml:"feature-summary"`
} `xml:"license-usage-summary"`
}
13 changes: 11 additions & 2 deletions pkg/rpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ package rpc
import (
"encoding/xml"
"fmt"

"log"

"github.com/czerwonk/junos_exporter/pkg/connector"
)

Expand All @@ -28,11 +26,18 @@ func WithSatellite() ClientOption {
}
}

func WithLicenseInformation() ClientOption {
return func(cl *Client) {
cl.license = true
}
}

// Client sends commands to JunOS and parses results
type Client struct {
conn *connector.SSHConnection
debug bool
satellite bool
license bool
}

// NewClient creates a new client to connect to
Expand Down Expand Up @@ -81,3 +86,7 @@ func (c *Client) Device() *connector.Device {
func (c *Client) IsSatelliteEnabled() bool {
return c.satellite
}

func (c *Client) IsScrapingLicenseEnabled() bool {
return c.license
}
4 changes: 4 additions & 0 deletions tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ func (cta *clientTracingAdapter) IsSatelliteEnabled() bool {
return cta.cl.IsSatelliteEnabled()
}

func (cta *clientTracingAdapter) IsScrapingLicenseEnabled() bool {
return cta.cl.IsScrapingLicenseEnabled()
}

// Device implements Device of the collector.Client interface
func (cta *clientTracingAdapter) Device() *connector.Device {
return cta.cl.Device()
Expand Down