diff --git a/README.md b/README.md index 2b843beb..6401fed1 100755 --- a/README.md +++ b/README.md @@ -51,6 +51,14 @@ Global: * `fortigate_current_sessions` * _System/HAChecksums_ * `fortigate_ha_member_has_role` + * _System/Ntp/Status_ + * `fortigate_system_ntp_delay_seconds` + * `fortigate_system_ntp_dispersion_seconds` + * `fortigate_system_ntp_dispersion_peer_seconds` + * `fortigate_system_ntp_expires_seconds` + * `fortigate_system_ntp_offset_seconds` + * `fortigate_system_ntp_reftime_seconds` + * `fortigate_system_ntp_stratum` * _License/Status_ * `fortigate_license_vdom_usage` * `fortigate_license_vdom_max` @@ -416,6 +424,7 @@ To improve security, limit permissions to required ones only (least privilege pr |System/HAStatistics | sysgrp.cfg |api/v2/monitor/system/ha-statistics
api/v2/cmdb/system/ha | |System/Interface | netgrp.cfg |api/v2/monitor/system/interface/select | |System/LinkMonitor | sysgrp.cfg |api/v2/monitor/system/link-monitor | +|System/Ntp/Status | netgrp.cfg |api/v2/monitor/system/ntp/status | |System/Resource/Usage | sysgrp.cfg |api/v2/monitor/system/resource/usage | |System/SensorInfo | sysgrp.cfg |api/v2/monitor/system/sensor-info | |System/Status | *any* |api/v2/monitor/system/status | diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go index 88d76fe6..78b31fa4 100644 --- a/pkg/probe/probe.go +++ b/pkg/probe/probe.go @@ -145,6 +145,7 @@ func (p *ProbeCollector) Probe(ctx context.Context, target map[string]string, hc {"System/HAStatistics", probeSystemHAStatistics}, {"System/Interface", probeSystemInterface}, {"System/LinkMonitor", probeSystemLinkMonitor}, + {"System/Ntp/Status", probeSystemNtpStatus}, {"System/Resource/Usage", probeSystemResourceUsage}, {"System/SDNConnector", probeSystemSDNConnector}, {"System/SensorInfo", probeSystemSensorInfo}, diff --git a/pkg/probe/system_ntp_status.go b/pkg/probe/system_ntp_status.go new file mode 100644 index 00000000..f66dc50a --- /dev/null +++ b/pkg/probe/system_ntp_status.go @@ -0,0 +1,106 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probe + +import ( + "log" + "strconv" + + "github.com/prometheus-community/fortigate_exporter/pkg/http" + "github.com/prometheus/client_golang/prometheus" +) + +func probeSystemNtpStatus(c http.FortiHTTP, meta *TargetMetadata) ([]prometheus.Metric, bool) { + var ( + ntpExpires = prometheus.NewDesc( + "fortigate_system_ntp_expires_seconds", + "NTP expire time, in seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpStratum = prometheus.NewDesc( + "fortigate_system_ntp_stratum", + "NTP stratum value", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpRefTime = prometheus.NewDesc( + "fortigate_system_ntp_reftime_seconds", + "NTP reftime in epoch seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpOffset = prometheus.NewDesc( + "fortigate_system_ntp_offset_seconds", + "NTP combined offset, in seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpDelay = prometheus.NewDesc( + "fortigate_system_ntp_delay_seconds", + "NTP round trip delay, in seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpDispersion = prometheus.NewDesc( + "fortigate_system_ntp_dispersion_seconds", + "NTP dispersion to primary clock, in seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ntpPeerDispersion = prometheus.NewDesc( + "fortigate_system_ntp_dispersion_peer_seconds", + "NTP peer dispersion, in seconds", + []string{"ip", "server", "reachable", "selected", "version", "vdom"}, nil, + ) + ) + + type SystemNtpStatus struct { + Ip string `json:"ip"` + Server string `json:"server"` + Reachable bool `json:"reachable"` + Expires int `json:"expires"` + Selected bool `json:"selected"` + Version int `json:"version"` + Stratum int `json:"stratum"` + Reftime int `json:"reftime"` + Offset float64 `json:"offset"` + Delay float64 `json:"delay"` + Dispersion float64 `json:"dispersion"` + PeerDispersion int `json:"peer_dispersion"` + } + + type SystemNtpStatusResult struct { + Results []SystemNtpStatus `json:"results"` + VDOM string `json:"vdom"` + } + + var result []SystemNtpStatusResult + if err := c.Get("api/v2/monitor/system/ntp/status","vdom=*",&result); err != nil { + log.Printf("Error: %v", err) + return nil, false + } + + m := []prometheus.Metric{} + if meta.VersionMajor >=7 && meta.VersionMinor >=4 { + for _, res := range result { + for _, r := range res.Results { + m = append(m, prometheus.MustNewConstMetric(ntpExpires, prometheus.GaugeValue, float64(r.Expires), r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpStratum, prometheus.GaugeValue, float64(r.Stratum), r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpRefTime, prometheus.CounterValue, float64(r.Reftime), r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpOffset, prometheus.GaugeValue, float64(r.Offset) * 0.001, r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpDelay, prometheus.GaugeValue, float64(r.Delay) * 0.001, r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpDispersion, prometheus.GaugeValue, float64(r.Dispersion) * 0.001, r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + m = append(m, prometheus.MustNewConstMetric(ntpPeerDispersion, prometheus.GaugeValue, float64(r.PeerDispersion) * 0.001, r.Ip, r.Server, strconv.FormatBool(r.Reachable), strconv.FormatBool(r.Reachable), strconv.Itoa(r.Version), res.VDOM)) + } + } + } else { + log.Printf("Not implemented in versions under 7.4") + } + return m, true +} \ No newline at end of file diff --git a/pkg/probe/system_ntp_status_test.go b/pkg/probe/system_ntp_status_test.go new file mode 100644 index 00000000..4c932ebf --- /dev/null +++ b/pkg/probe/system_ntp_status_test.go @@ -0,0 +1,116 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probe + +import ( + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestSystemNtpStatus_7_4_0(t *testing.T) { + c := newFakeClient() + c.prepare("api/v2/monitor/system/ntp/status", "testdata/system-ntp-status.jsonnet") + meta := &TargetMetadata{ + VersionMajor: 7, + VersionMinor: 4, + } + r := prometheus.NewPedanticRegistry() + if !testProbeWithMetadata(probeSystemNtpStatus, c, meta, r) { + t.Errorf("probeSystemNtpStatus() returned non-success") + } + + em := `# HELP fortigate_system_ntp_delay_seconds NTP round trip delay, in seconds + # TYPE fortigate_system_ntp_delay_seconds gauge + fortigate_system_ntp_delay_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 0.324 + fortigate_system_ntp_delay_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 0.324 + fortigate_system_ntp_delay_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 0.324 + fortigate_system_ntp_delay_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 0.324 + fortigate_system_ntp_delay_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 0.324 + fortigate_system_ntp_delay_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 0.324 + # HELP fortigate_system_ntp_dispersion_seconds NTP dispersion to primary clock, in seconds + # TYPE fortigate_system_ntp_dispersion_seconds gauge + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 0.342 + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 0.342 + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 0.342 + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 0.342 + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 0.342 + fortigate_system_ntp_dispersion_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 0.342 + # HELP fortigate_system_ntp_dispersion_peer_seconds NTP peer dispersion, in seconds + # TYPE fortigate_system_ntp_dispersion_peer_seconds gauge + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 0.123 + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 0.123 + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 0.123 + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 0.123 + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 0.123 + fortigate_system_ntp_dispersion_peer_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 0.123 + # HELP fortigate_system_ntp_expires_seconds NTP expire time, in seconds + # TYPE fortigate_system_ntp_expires_seconds gauge + fortigate_system_ntp_expires_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 145438 + fortigate_system_ntp_expires_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 145438 + fortigate_system_ntp_expires_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 124438 + fortigate_system_ntp_expires_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 124438 + fortigate_system_ntp_expires_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 245438 + fortigate_system_ntp_expires_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 245438 + # HELP fortigate_system_ntp_offset_seconds NTP combined offset, in seconds + # TYPE fortigate_system_ntp_offset_seconds gauge + fortigate_system_ntp_offset_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 5.482 + fortigate_system_ntp_offset_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 5.482 + fortigate_system_ntp_offset_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 5.482 + fortigate_system_ntp_offset_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 5.482 + fortigate_system_ntp_offset_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 5.482 + fortigate_system_ntp_offset_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 5.482 + # HELP fortigate_system_ntp_reftime_seconds NTP reftime in epoch seconds + # TYPE fortigate_system_ntp_reftime_seconds counter + fortigate_system_ntp_reftime_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 85742 + fortigate_system_ntp_reftime_seconds{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 85742 + fortigate_system_ntp_reftime_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 85742 + fortigate_system_ntp_reftime_seconds{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 85742 + fortigate_system_ntp_reftime_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 85742 + fortigate_system_ntp_reftime_seconds{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 85742 + # HELP fortigate_system_ntp_stratum NTP stratum value + # TYPE fortigate_system_ntp_stratum gauge + fortigate_system_ntp_stratum{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="google",version="1035"} 3 + fortigate_system_ntp_stratum{ip="127.0.0.1",reachable="true",selected="true",server="HA-TEST",vdom="vdomtest",version="1035"} 3 + fortigate_system_ntp_stratum{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="google",version="1035"} 3 + fortigate_system_ntp_stratum{ip="127.0.0.2",reachable="false",selected="false",server="HA-CAP",vdom="vdomtest",version="1035"} 3 + fortigate_system_ntp_stratum{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="google",version="1035"} 3 + fortigate_system_ntp_stratum{ip="127.0.0.3",reachable="true",selected="true",server="HA-CODE",vdom="vdomtest",version="1035"} 3 + ` + + if err := testutil.CollectAndCompare(r, strings.NewReader(em)); err != nil { + t.Fatalf("metric compare: err %v", err) + } +} + +func TestSystemNtpStatus(t *testing.T) { + c := newFakeClient() + c.prepare("api/v2/monitor/system/ntp/status", "testdata/system-ntp-status.jsonnet") + meta := &TargetMetadata{ + VersionMajor: 7, + VersionMinor: 2, + } + r := prometheus.NewPedanticRegistry() + if !testProbeWithMetadata(probeSystemNtpStatus, c, meta, r) { + t.Errorf("probeSystemNtpStatus() returned non-success") + } + + em := `` + + if err := testutil.CollectAndCompare(r, strings.NewReader(em)); err != nil { + t.Fatalf("metric compare: err %v", err) + } +} \ No newline at end of file diff --git a/pkg/probe/testdata/system-ntp-status.jsonnet b/pkg/probe/testdata/system-ntp-status.jsonnet new file mode 100644 index 00000000..20c2c285 --- /dev/null +++ b/pkg/probe/testdata/system-ntp-status.jsonnet @@ -0,0 +1,112 @@ +[ + { + "http_method":"GET", + "results":[ + { + "ip": "127.0.0.1", + "server": "HA-TEST", + "reachable": true, + "expires": 145438, + "selected": false, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + }, + { + "ip": "127.0.0.2", + "server": "HA-CAP", + "reachable": false, + "expires": 124438, + "selected": true, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + }, + { + "ip": "127.0.0.3", + "server": "HA-CODE", + "reachable": true, + "expires": 245438, + "selected": true, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + } + ], + "vdom":"google", + "path":"system", + "name":"sdn-connector", + "action":"status", + "status":"success", + "serial":"FGABCDEF12345678", + "version":"v7.0.9", + "build":444 + }, + { + "http_method":"GET", + "results":[ + { + "ip": "127.0.0.1", + "server": "HA-TEST", + "reachable": true, + "expires": 145438, + "selected": false, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + }, + { + "ip": "127.0.0.2", + "server": "HA-CAP", + "reachable": false, + "expires": 124438, + "selected": true, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + }, + { + "ip": "127.0.0.3", + "server": "HA-CODE", + "reachable": true, + "expires": 245438, + "selected": true, + "version": 1035, + "stratum": 3, + "reftime": 85742, + "offset": 5482, + "delay": 324, + "dispersion": 342, + "peer_dispersion": 123 + } + ], + "vdom":"vdomtest", + "path":"system", + "name":"sdn-connector", + "action":"status", + "status":"success", + "serial":"FGABCDEF12345678", + "version":"v7.0.9", + "build":444 + } +] \ No newline at end of file