master
go 131 lines 4.02 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package panos
4
5 import (
6 "context"
7 "fmt"
8 "strings"
9 )
10
11 type haResult struct {
12 Enabled string `xml:"enabled"`
13 Group haGroup `xml:"group"`
14 }
15
16 type haGroup struct {
17 Mode string `xml:"mode"`
18 RunningSync string `xml:"running-sync"`
19 LocalInfo haInfo `xml:"local-info"`
20 PeerInfo haInfo `xml:"peer-info"`
21 }
22
23 type haInfo struct {
24 State string `xml:"state"`
25 ConnStatus string `xml:"conn-status"`
26 StateSync string `xml:"state-sync"`
27 ConnHA1 haConn `xml:"conn-ha1"`
28 ConnHA1B haConn `xml:"conn-ha1-backup"`
29 ConnHA2 haConn `xml:"conn-ha2"`
30 ConnHA2B haConn `xml:"conn-ha2-backup"`
31 }
32
33 type haConn struct {
34 Status string `xml:"conn-status"`
35 }
36
37 func (c *Collector) collectHAMetrics(ctx context.Context) (bool, error) {
38 body, err := c.apiClient.op(ctx, haStateCommand)
39 if err != nil {
40 return false, fmt.Errorf("ha metricset: %s API call: %w", panosCommandName(haStateCommand), err)
41 }
42
43 ha, err := parseHAState(body)
44 if err != nil {
45 return false, fmt.Errorf("ha metricset: %s response: %w", panosCommandName(haStateCommand), err)
46 }
47 if firstNonEmpty(ha.Enabled, ha.Group.LocalInfo.State, ha.Group.PeerInfo.State) == "" {
48 return false, fmt.Errorf("ha metricset: %s response: %w", panosCommandName(haStateCommand), missingPANOSResultError{expected: "<enabled> or <group>"})
49 }
50
51 enabled, err := c.haEnabledStatus(ha)
52 if err != nil {
53 return false, fmt.Errorf("ha metricset: %s response: %w", panosCommandName(haStateCommand), err)
54 }
55 if !enabled && firstNonEmpty(ha.Group.LocalInfo.State, ha.Group.PeerInfo.State, ha.Group.RunningSync) == "" {
56 observeStateSet(c.metrics.ha.status, "disabled")
57 return true, nil
58 }
59
60 localState := normalizeHAState(ha.Group.LocalInfo.State)
61 peerState := normalizeHAState(ha.Group.PeerInfo.State)
62 stateSync := firstNonEmpty(ha.Group.RunningSync, ha.Group.LocalInfo.StateSync)
63
64 observeStateSet(c.metrics.ha.status, boolState(enabled, "enabled", "disabled"))
65 observeStateSet(c.metrics.ha.localState, localState)
66 observeStateSet(c.metrics.ha.peerState, peerState)
67 if ha.Group.PeerInfo.ConnStatus != "" {
68 observeStateSet(c.metrics.ha.peerConnectionStatus, normalizeUpDownState(ha.Group.PeerInfo.ConnStatus))
69 }
70 if stateSync != "" {
71 observeStateSet(c.metrics.ha.stateSync, normalizeHASyncState(stateSync))
72 }
73 c.observeHALinkStatus("ha1", ha.Group.PeerInfo.ConnHA1.Status)
74 c.observeHALinkStatus("ha1_backup", ha.Group.PeerInfo.ConnHA1B.Status)
75 c.observeHALinkStatus("ha2", ha.Group.PeerInfo.ConnHA2.Status)
76 c.observeHALinkStatus("ha2_backup", ha.Group.PeerInfo.ConnHA2B.Status)
77 return true, nil
78 }
79
80 func (c *Collector) observeHALinkStatus(link, status string) {
81 state := normalizeUpDownState(status)
82 if state == "" {
83 return
84 }
85
86 observeStateSetVec(c.metrics.ha.linkStatus, state, link)
87 }
88
89 func parseHAState(body []byte) (haResult, error) {
90 var result haResult
91 if err := decodePANOSResult(body, "PAN-OS HA response", &result); err != nil {
92 return haResult{}, err
93 }
94 return result, nil
95 }
96
97 func normalizeHAState(state string) string {
98 state = strings.ToLower(strings.TrimSpace(state))
99 state = strings.ReplaceAll(state, "-", "_")
100 state = strings.ReplaceAll(state, " ", "_")
101 switch state {
102 case "":
103 return ""
104 case "active", "passive", "suspended", "unknown":
105 return state
106 case "nonfunctional", "non_functional", "non_function":
107 return "non_functional"
108 default:
109 return "unknown"
110 }
111 }
112
113 func (c *Collector) haEnabledStatus(ha haResult) (bool, error) {
114 if strings.TrimSpace(ha.Enabled) != "" {
115 return parsePANOSAffirmativeField("HA enabled", ha.Enabled)
116 }
117 return firstNonEmpty(ha.Group.LocalInfo.State, ha.Group.PeerInfo.State, ha.Group.RunningSync) != "", nil
118 }
119
120 func normalizeHASyncState(v string) string {
121 switch strings.ToLower(strings.TrimSpace(v)) {
122 case "synchronized", "complete":
123 return "synchronized"
124 case "not synchronized", "not-synchronized", "not_synchronized", "unsynchronized", "out of sync", "out-of-sync", "out_of_sync", "incomplete", "syncing", "synchronizing":
125 return "not_synchronized"
126 case "":
127 return ""
128 default:
129 return "unknown"
130 }
131 }