diff --git a/README.md b/README.md index 513dcb14..6da6f1d3 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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" @@ -87,7 +98,8 @@ 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" @@ -95,6 +107,15 @@ States map to human readable names like this: 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 diff --git a/collectors.go b/collectors.go index 450f93c3..2b9d1e20 100644 --- a/collectors.go +++ b/collectors.go @@ -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) diff --git a/internal/config/config.go b/internal/config/config.go index dc736290..fdd5cd23 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 @@ -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 diff --git a/junos_collector.go b/junos_collector.go index 7bdeeb8c..c2aa4c93 100644 --- a/junos_collector.go +++ b/junos_collector.go @@ -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 } diff --git a/main.go b/main.go index fa5f0f64..4ef70a6f 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ 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") @@ -74,6 +74,7 @@ var ( 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") @@ -242,6 +243,7 @@ func loadConfigFromFlags() *config.Config { f.BFD = *bfdEnabled f.VPWS = *vpwsEnabled f.MPLSLSP = *mplsLSPEnabled + f.License = *licenseEnabled return c } diff --git a/pkg/collector/rpc_collector.go b/pkg/collector/rpc_collector.go index 585c30eb..c5a81cd7 100644 --- a/pkg/collector/rpc_collector.go +++ b/pkg/collector/rpc_collector.go @@ -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 diff --git a/pkg/features/system/collector.go b/pkg/features/system/collector.go index 0fd585c7..3cc5f0fe 100644 --- a/pkg/features/system/collector.go +++ b/pkg/features/system/collector.go @@ -7,6 +7,8 @@ import ( "regexp" "strconv" "strings" + "time" + "math" "github.com/czerwonk/junos_exporter/pkg/collector" "github.com/prometheus/client_golang/prometheus" @@ -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+).*`) @@ -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 @@ -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 @@ -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 } @@ -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...) + } + } +} + diff --git a/pkg/features/system/rpc.go b/pkg/features/system/rpc.go index 3b1f8001..22b9255f 100644 --- a/pkg/features/system/rpc.go +++ b/pkg/features/system/rpc.go @@ -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"` +} \ No newline at end of file diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 413ea55d..f8bacf52 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -5,9 +5,7 @@ package rpc import ( "encoding/xml" "fmt" - "log" - "github.com/czerwonk/junos_exporter/pkg/connector" ) @@ -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 @@ -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 +} diff --git a/tracing.go b/tracing.go index cf2fecc8..33c5a196 100644 --- a/tracing.go +++ b/tracing.go @@ -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()