ctx_checker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/COUNTER_CONTRACT.md +33 -0
- data/LICENSE +22 -0
- data/README-zh.md +93 -0
- data/README.md +93 -0
- data/docs/architecture-zh.md +487 -0
- data/docs/release.md +13 -0
- data/examples/offline.rb +17 -0
- data/lib/ctx_checker/applicability.rb +187 -0
- data/lib/ctx_checker/checks/aggregation.rb +73 -0
- data/lib/ctx_checker/checks/alarms.rb +25 -0
- data/lib/ctx_checker/checks/base.rb +66 -0
- data/lib/ctx_checker/checks/clock.rb +30 -0
- data/lib/ctx_checker/checks/counters.rb +77 -0
- data/lib/ctx_checker/checks/device_counters.rb +32 -0
- data/lib/ctx_checker/checks/hardware.rb +45 -0
- data/lib/ctx_checker/checks/interfaces.rb +35 -0
- data/lib/ctx_checker/checks/ntp.rb +47 -0
- data/lib/ctx_checker/checks/optics.rb +107 -0
- data/lib/ctx_checker/checks/resources.rb +63 -0
- data/lib/ctx_checker/checks/routing.rb +86 -0
- data/lib/ctx_checker/checks/temperature.rb +53 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_clock_detail.rb +56 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_environment_all.rb +153 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_etherchannel_summary.rb +110 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_facility_alarm_status.rb +41 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_interfaces.rb +140 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_interfaces_transceiver_detail.rb +107 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_bgp_summary.rb +164 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_ospf_neighbor.rb +96 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_traffic.rb +65 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_memory_statistics.rb +36 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_ntp_status.rb +44 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_processes_cpu.rb +45 -0
- data/lib/ctx_checker/cisco_ios/parsers/system_values.rb +62 -0
- data/lib/ctx_checker/cisco_ios/profile.rb +39 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_bgp_ipv4_unicast_summary.rb +69 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_clock.rb +13 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_environment.rb +157 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_interface.rb +78 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_interface_transceiver_details.rb +141 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_ip_ospf_neighbors.rb +14 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_ip_traffic.rb +151 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_ntp_peer_status.rb +116 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_port_channel_summary.rb +21 -0
- data/lib/ctx_checker/cisco_nxos/parsers/show_system_resources.rb +89 -0
- data/lib/ctx_checker/cisco_nxos/profile.rb +35 -0
- data/lib/ctx_checker/collector.rb +175 -0
- data/lib/ctx_checker/evaluator.rb +49 -0
- data/lib/ctx_checker/failure.rb +24 -0
- data/lib/ctx_checker/finding.rb +19 -0
- data/lib/ctx_checker/h3c/parsers/counters_table.rb +56 -0
- data/lib/ctx_checker/h3c/parsers/display_alarm_active.rb +43 -0
- data/lib/ctx_checker/h3c/parsers/display_bgp_peer.rb +44 -0
- data/lib/ctx_checker/h3c/parsers/display_clock.rb +72 -0
- data/lib/ctx_checker/h3c/parsers/display_counters_inbound_interface.rb +21 -0
- data/lib/ctx_checker/h3c/parsers/display_counters_outbound_interface.rb +21 -0
- data/lib/ctx_checker/h3c/parsers/display_cpu_usage.rb +64 -0
- data/lib/ctx_checker/h3c/parsers/display_environment.rb +74 -0
- data/lib/ctx_checker/h3c/parsers/display_fan.rb +72 -0
- data/lib/ctx_checker/h3c/parsers/display_interface.rb +127 -0
- data/lib/ctx_checker/h3c/parsers/display_link_aggregation_verbose.rb +72 -0
- data/lib/ctx_checker/h3c/parsers/display_memory.rb +132 -0
- data/lib/ctx_checker/h3c/parsers/display_ntp_service_status.rb +84 -0
- data/lib/ctx_checker/h3c/parsers/display_ospf_peer.rb +47 -0
- data/lib/ctx_checker/h3c/parsers/display_power.rb +63 -0
- data/lib/ctx_checker/h3c/parsers/display_transceiver_diagnosis.rb +109 -0
- data/lib/ctx_checker/h3c/parsers/system_values.rb +46 -0
- data/lib/ctx_checker/h3c/profile.rb +49 -0
- data/lib/ctx_checker/hillstone/parsers/show_clock.rb +68 -0
- data/lib/ctx_checker/hillstone/parsers/show_cpu.rb +44 -0
- data/lib/ctx_checker/hillstone/parsers/show_cpu_cntr.rb +114 -0
- data/lib/ctx_checker/hillstone/parsers/show_environment.rb +90 -0
- data/lib/ctx_checker/hillstone/parsers/show_interface.rb +82 -0
- data/lib/ctx_checker/hillstone/parsers/show_interface_counter.rb +53 -0
- data/lib/ctx_checker/hillstone/parsers/show_ip_bgp_summary.rb +22 -0
- data/lib/ctx_checker/hillstone/parsers/show_ip_ospf_neighbor.rb +21 -0
- data/lib/ctx_checker/hillstone/parsers/show_memory.rb +85 -0
- data/lib/ctx_checker/hillstone/parsers/show_ntp_status.rb +110 -0
- data/lib/ctx_checker/hillstone/parsers/show_transceiver.rb +101 -0
- data/lib/ctx_checker/hillstone/profile.rb +40 -0
- data/lib/ctx_checker/observation.rb +165 -0
- data/lib/ctx_checker/observation_merger.rb +80 -0
- data/lib/ctx_checker/palo_alto/parsers/show_clock.rb +50 -0
- data/lib/ctx_checker/palo_alto/parsers/show_counter_global.rb +47 -0
- data/lib/ctx_checker/palo_alto/parsers/show_counter_interface_all.rb +68 -0
- data/lib/ctx_checker/palo_alto/parsers/show_interface_all.rb +70 -0
- data/lib/ctx_checker/palo_alto/parsers/show_lacp_aggregate_ethernet.rb +52 -0
- data/lib/ctx_checker/palo_alto/parsers/show_ntp.rb +78 -0
- data/lib/ctx_checker/palo_alto/parsers/show_routing_protocol_bgp_peer.rb +66 -0
- data/lib/ctx_checker/palo_alto/parsers/show_routing_protocol_ospf_neighbor.rb +54 -0
- data/lib/ctx_checker/palo_alto/parsers/show_system_environmentals.rb +142 -0
- data/lib/ctx_checker/palo_alto/parsers/show_system_resources.rb +102 -0
- data/lib/ctx_checker/palo_alto/parsers/show_system_state_alarms.rb +54 -0
- data/lib/ctx_checker/palo_alto/parsers/show_transceiver_all.rb +44 -0
- data/lib/ctx_checker/palo_alto/profile.rb +39 -0
- data/lib/ctx_checker/parser_registry.rb +43 -0
- data/lib/ctx_checker/parsers/base.rb +68 -0
- data/lib/ctx_checker/parsers/interface_names.rb +65 -0
- data/lib/ctx_checker/parsers/scoped_result.rb +35 -0
- data/lib/ctx_checker/policy.rb +104 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_l2_lacp_dump.rb +48 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_l2_trunk.rb +46 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_l3_bgp_summary.rb +22 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_l3_ip.rb +71 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_l3_ospf_nbr.rb +22 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_link.rb +44 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_sys_fan.rb +47 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_sys_general.rb +48 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_sys_ps.rb +34 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_sys_temp.rb +53 -0
- data/lib/ctx_checker/radware_alteon/parsers/info_transceiver.rb +68 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_mp_cpu.rb +39 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_mp_mem.rb +58 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_port_ether.rb +67 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_port_if.rb +74 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_sp_allutils.rb +99 -0
- data/lib/ctx_checker/radware_alteon/parsers/stats_sys_ntp.rb +86 -0
- data/lib/ctx_checker/radware_alteon/port_counter_parser.rb +34 -0
- data/lib/ctx_checker/radware_alteon/profile.rb +145 -0
- data/lib/ctx_checker/radware_alteon.rb +8 -0
- data/lib/ctx_checker/report.rb +112 -0
- data/lib/ctx_checker/runtime_parser.rb +234 -0
- data/lib/ctx_checker/snapshot.rb +44 -0
- data/lib/ctx_checker/system_policy.rb +70 -0
- data/lib/ctx_checker/system_record.rb +81 -0
- data/lib/ctx_checker/version.rb +6 -0
- data/lib/ctx_checker.rb +17 -0
- metadata +271 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module PaloAlto
|
|
8
|
+
module Parsers
|
|
9
|
+
# PAN-OS 分别展示物理状态与逻辑接口清单。
|
|
10
|
+
class ShowInterfaceAll < CtxChecker::Parsers::Base
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# 解析 show interface all,保留不完整或不支持的证据。
|
|
14
|
+
def parse_output(text, complete:)
|
|
15
|
+
text = clean(text)
|
|
16
|
+
rows, issues, section, totals, counts, duplicates = {}, [], nil, {}, Hash.new(0), []
|
|
17
|
+
logical_names = {}
|
|
18
|
+
text.each_line do |raw|
|
|
19
|
+
line = raw.strip
|
|
20
|
+
if (match = /\Atotal configured (hardware|logical) interfaces:\s*(\d+)\z/i.match(line))
|
|
21
|
+
section = match[1].downcase.to_sym
|
|
22
|
+
totals[section] = match[2].to_i
|
|
23
|
+
elsif (match = /\Aaggregation groups:\s*(\d+)\z/i.match(line))
|
|
24
|
+
section = :aggregation
|
|
25
|
+
totals[section] = match[1].to_i
|
|
26
|
+
elsif line.empty? || line.match?(/\A[- ]+\z/) || line.match?(/\Aname\s+id\s+/i)
|
|
27
|
+
next
|
|
28
|
+
elsif section == :hardware && (match = /\A(\S+)\s+\d+\s+\S+\/\S+\/(up|down)(?:\([^\s]*\))?\s+[\da-f:]+\z/i.match(line))
|
|
29
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(match[1], platform: :palo_alto)
|
|
30
|
+
if rows.key?(name)
|
|
31
|
+
issues << { code: :duplicate_interface, interface: name }
|
|
32
|
+
duplicates << name
|
|
33
|
+
end
|
|
34
|
+
rows[name] = record(name).merge(oper_status: match[2].downcase.to_sym)
|
|
35
|
+
counts[section] += 1
|
|
36
|
+
elsif section == :aggregation && line.match?(/\Aae\d+ members:\z/i)
|
|
37
|
+
counts[section] += 1
|
|
38
|
+
elsif section == :aggregation && line.match?(/\Aethernet\d+\/\d+(?:\s+ethernet\d+\/\d+)*\z/i)
|
|
39
|
+
next # 清单中的成员关系不表示 LACP 选中状态或运行状态。
|
|
40
|
+
elsif section == :logical && (match = /\A(\S+)\s+\d+\s+\d+\s+.+\z/.match(line))
|
|
41
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(match[1], platform: :palo_alto)
|
|
42
|
+
if logical_names.key?(name)
|
|
43
|
+
issues << { code: :duplicate_interface, interface: name }
|
|
44
|
+
duplicates << name
|
|
45
|
+
end
|
|
46
|
+
logical_names[name] = true
|
|
47
|
+
rows[name] ||= record(name)
|
|
48
|
+
counts[section] += 1
|
|
49
|
+
elsif section == :logical && line.match?(%r{\A[\da-f:.]+/\d+\z}i)
|
|
50
|
+
next # 地址续行不携带接口状态。
|
|
51
|
+
else
|
|
52
|
+
issues << { code: :unrecognized_interface_line }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
totals.each do |kind, total|
|
|
56
|
+
issues << { code: :count_mismatch, section: kind, expected: total, observed: counts[kind] } if total != counts[kind]
|
|
57
|
+
end
|
|
58
|
+
duplicates.each { |name| rows.delete(name) }
|
|
59
|
+
build_result(rows.values, complete: complete, recognized: totals.key?(:hardware), issues: issues, identity: [ :name ])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 先创建状态未知的接口身份,再由所属表补充明确证据。
|
|
63
|
+
def record(name)
|
|
64
|
+
{ name: name, kind: CtxChecker::Parsers::InterfaceNames.kind(name), admin_status: :unknown, oper_status: :unknown,
|
|
65
|
+
protocol_status: :unknown, parent: name.match?(/\A(?:ethernet|ae).*\./) ? name.split(".").first : nil }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module PaloAlto
|
|
8
|
+
module Parsers
|
|
9
|
+
# PAN-OS LACP 回显中的成组关系与选中状态是独立事实。
|
|
10
|
+
class ShowLacpAggregateEthernet < CtxChecker::Parsers::Base
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# 解析 show lacp aggregate ethernet,保留不完整或不支持的证据。
|
|
14
|
+
def parse_output(text, complete:)
|
|
15
|
+
text = clean(text)
|
|
16
|
+
rows, issues, group, header, groups = [], [], nil, false, {}
|
|
17
|
+
text.each_line do |raw|
|
|
18
|
+
line = raw.strip
|
|
19
|
+
if (match = /\AAE group:\s*(ae\d+)\z/i.match(line))
|
|
20
|
+
group = match[1].downcase
|
|
21
|
+
groups[group] ||= 0
|
|
22
|
+
elsif line.match?(/\AMembers:\s+Bndl\s+Rx state\s+Mux state\s+Sel state\z/i)
|
|
23
|
+
header = true
|
|
24
|
+
elsif (match = /\A(ethernet\d+\/\d+)\s+(yes|no)\s+(\S+)\s+(\S+)\s+(Selected|Unselected|Standby)(?:\(.*\))?\z/i.match(line))
|
|
25
|
+
if group
|
|
26
|
+
state = if match[5].casecmp?("Standby")
|
|
27
|
+
:standby
|
|
28
|
+
elsif match[5].casecmp?("Unselected")
|
|
29
|
+
:unselected
|
|
30
|
+
elsif match[2].casecmp?("yes") && match[4].casecmp?("Tx_Rx")
|
|
31
|
+
:active
|
|
32
|
+
else
|
|
33
|
+
:unknown
|
|
34
|
+
end
|
|
35
|
+
rows << { group: group, member: match[1].downcase, member_status: state, oper_status: :unknown }
|
|
36
|
+
groups[group] += 1
|
|
37
|
+
else
|
|
38
|
+
issues << { code: :aggregation_group_missing }
|
|
39
|
+
end
|
|
40
|
+
elsif line.match?(/\Aethernet\d+\//i)
|
|
41
|
+
issues << { code: :invalid_aggregation_member }
|
|
42
|
+
elsif !line.empty? && !line.match?(/\A(?:\*+|-+|LACP:|Status:|Mode:|Rate:|Max-port:|Fast-failover:|Pre-negotiation:|Local:|Partner:|System Priority:|System MAC:|Key:)/i)
|
|
43
|
+
issues << { code: :unrecognized_aggregation_line }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
groups.each { |name, count| issues << { code: :aggregation_members_missing, group: name } if count.zero? }
|
|
47
|
+
build_result(rows, complete: complete, recognized: header && rows.any?, issues: issues, identity: %i[group member])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module PaloAlto
|
|
7
|
+
module Parsers
|
|
8
|
+
# 对等体可达与 LOCAL 回退不能证明 NTP 已同步。
|
|
9
|
+
class ShowNtp < CtxChecker::Parsers::Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# 解析 show ntp,保留不完整或不支持的证据。
|
|
13
|
+
def parse_output(text, complete:)
|
|
14
|
+
source = clean(text)
|
|
15
|
+
rows, globals, issues = [], [], []
|
|
16
|
+
current, fields, header, peer_fields = nil, {}, false, {}
|
|
17
|
+
source.each_line.with_index(1) do |raw, number|
|
|
18
|
+
line = raw.strip
|
|
19
|
+
next if line.empty?
|
|
20
|
+
if line == "NTP state:"
|
|
21
|
+
header = true
|
|
22
|
+
elsif (match = /\ANTP synched to (\S+)\z/i.match(line))
|
|
23
|
+
globals << { component: "system", synchronized: !match[1].casecmp?("LOCAL"), reference: match[1] }
|
|
24
|
+
current = nil
|
|
25
|
+
elsif line.match?(/\ANTP not synched, using local clock\z/i)
|
|
26
|
+
globals << { component: "system", synchronized: false, reference: "LOCAL" }
|
|
27
|
+
current = nil
|
|
28
|
+
elsif (match = /\ANTP server (\S+) connected: (True|False)\z/i.match(line))
|
|
29
|
+
row = { component: "peer/#{match[1]}", peer: match[1], synchronized: nil }
|
|
30
|
+
rows << row
|
|
31
|
+
peer_fields[row.object_id] = { "reachable" => match[2].casecmp?("true") ? "yes" : "no" }
|
|
32
|
+
current = nil
|
|
33
|
+
elsif (match = /\ANTP server:\s*(\S+)\z/.match(line))
|
|
34
|
+
current = { component: "peer/#{match[1]}", peer: match[1], synchronized: nil }
|
|
35
|
+
rows << current
|
|
36
|
+
fields = {}
|
|
37
|
+
peer_fields[current.object_id] = fields
|
|
38
|
+
elsif current && (match = /\A(status|reachable|authentication-type):\s*(\S+)\z/.match(line))
|
|
39
|
+
key, value = match[1], match[2].downcase
|
|
40
|
+
if fields.key?(key)
|
|
41
|
+
issues << { code: :duplicate_ntp_field, line: number }
|
|
42
|
+
current[:synchronized] = nil if key == "status"
|
|
43
|
+
elsif key == "status"
|
|
44
|
+
current[:synchronized] = { "synched" => true, "rejected" => false, "unsynched" => false }[value]
|
|
45
|
+
# 可用备份对等体可以参与同步,但不是当前选中的同步源。
|
|
46
|
+
issues << { code: :unknown_ntp_status, line: number } if current[:synchronized].nil? && value != "available"
|
|
47
|
+
elsif key == "reachable" && !%w[yes no].include?(value)
|
|
48
|
+
issues << { code: :unknown_ntp_reachability, line: number }
|
|
49
|
+
end
|
|
50
|
+
fields[key] = value
|
|
51
|
+
else
|
|
52
|
+
issues << { code: :unrecognized_ntp_row, line: number }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
rows.each do |row|
|
|
56
|
+
if row[:synchronized] == true && peer_fields.fetch(row.object_id, {})["reachable"] == "no"
|
|
57
|
+
row[:synchronized] = nil
|
|
58
|
+
issues << { code: :conflicting_ntp_state, component: row[:component] }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
globals.each do |global|
|
|
62
|
+
next unless global[:synchronized] == true
|
|
63
|
+
peers = rows.select { |row| row[:peer] == global[:reference] }
|
|
64
|
+
if peers.any? { |row| row[:synchronized] == false || peer_fields.fetch(row.object_id, {})["reachable"] == "no" }
|
|
65
|
+
global[:synchronized] = nil
|
|
66
|
+
issues << { code: :conflicting_ntp_state, component: "system" }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
if globals.empty? && header
|
|
70
|
+
globals << { component: "system", synchronized: nil }
|
|
71
|
+
issues << { code: :ntp_system_status_missing }
|
|
72
|
+
end
|
|
73
|
+
build_result(globals + rows, complete: complete, recognized: header && globals.any?, issues: issues, identity: [ :component ])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module PaloAlto
|
|
7
|
+
module Parsers
|
|
8
|
+
# 同一对等体可能报告多个地址族。
|
|
9
|
+
class ShowRoutingProtocolBgpPeer < CtxChecker::Parsers::Base
|
|
10
|
+
STATES = %w[established idle active connect opensent openconfirm shutdown].freeze
|
|
11
|
+
FIELDS = /\A(?:Peer:|virtual router:|Peer router id:|Remote AS:|Peer group:|Peer status:|Password set:|Passive:|Multi-hop TTL:|Remote Address:|Local Address:|\(R\) reflector client:|same confederation:|send aggr confed as-path:|peering type:|Connect-Retry interval:|Open Delay:|Idle Hold:|Prefix limit:|Holdtime:|Keep-Alive interval:|Update messages:|Total messages:|Last update age:|Last error:|Flap counts:|\(R\) ORF entries:|Nexthop set to self:|use 3rd party as next-hop:|override nexthop to peer:|remove private AS number:|Capability:|Prefix counter for:|Incoming Prefix:|Outgoing Prefix:|Advertised Prefix:|[-=]+\z)/i
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# 解析 show routing protocol bgp peer,保留不完整或不支持的证据。
|
|
16
|
+
def parse_output(text, complete:)
|
|
17
|
+
text = clean(text)
|
|
18
|
+
rows, issues = [], []
|
|
19
|
+
text.split(/(?=^\s*Peer:\s)/i).each do |block|
|
|
20
|
+
next if block.strip.empty? || block.strip.match?(/\A=+\z/)
|
|
21
|
+
unless block.match?(/\A\s*Peer:\s/i)
|
|
22
|
+
issues << { code: :unrecognized_bgp_block }
|
|
23
|
+
next
|
|
24
|
+
end
|
|
25
|
+
if block.lines.any? { |line| !line.strip.empty? && !FIELDS.match?(line.strip) }
|
|
26
|
+
issues << { code: :unrecognized_bgp_field }
|
|
27
|
+
end
|
|
28
|
+
if [ "Remote Address", "Peer status", "Remote AS", "virtual router" ].any? { |field| block.scan(/^\s*#{Regexp.escape(field)}:/i).size > 1 }
|
|
29
|
+
issues << { code: :duplicate_bgp_field }
|
|
30
|
+
next
|
|
31
|
+
end
|
|
32
|
+
address = block[/^\s*Remote Address:\s*(\S+)/i, 1]
|
|
33
|
+
peer = address&.sub(/\A\[([^\]]+)\]:\d+\z/, '\1')
|
|
34
|
+
peer = peer.sub(/:\d+\z/, "") if peer&.match?(/\A\d+(?:\.\d+){3}:\d+\z/)
|
|
35
|
+
unless CtxParser::Values.valid_host?(peer)
|
|
36
|
+
issues << { code: :bgp_peer_address_missing }
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
peer = CtxParser::Values.host(peer)
|
|
40
|
+
state = block[/^\s*Peer status:\s*([^,\n]+)/i, 1]&.strip&.downcase
|
|
41
|
+
state = STATES.include?(state) ? state.to_sym : :unknown
|
|
42
|
+
remote_as = block[/^\s*Remote AS:\s*(\d+)\s*$/i, 1]&.to_i
|
|
43
|
+
if remote_as && !(1..4_294_967_295).cover?(remote_as)
|
|
44
|
+
issues << { code: :invalid_bgp_asn }
|
|
45
|
+
remote_as = nil
|
|
46
|
+
end
|
|
47
|
+
issues << { code: :bgp_peer_fields_missing } if state == :unknown || remote_as.nil?
|
|
48
|
+
base = { vrf: block[/^\s*virtual router:\s*(\S+)/i, 1], peer: peer, state: state, remote_as: remote_as }
|
|
49
|
+
families = block.split(/(?=^\s*Prefix counter for:)/i).drop(1)
|
|
50
|
+
if families.empty?
|
|
51
|
+
rows << base.merge(afi_safi: nil, received_prefixes: nil)
|
|
52
|
+
else
|
|
53
|
+
families.each do |family|
|
|
54
|
+
scope = /^[ \t]*Prefix counter for:[ \t]*bgpAfiIpv([46])[ \t]*\/[ \t]*(unicast|multicast)[ \t]*$/i.match(family)
|
|
55
|
+
prefixes = family[/^\s*Incoming Prefix:\s*Accepted \d+, Rejected \d+, Policy Rej \d+, Total (\d+)\s*$/i, 1]
|
|
56
|
+
issues << { code: :bgp_address_family_unknown } unless scope
|
|
57
|
+
rows << base.merge(afi_safi: scope ? "ipv#{scope[1]}_#{scope[2].downcase}" : nil, received_prefixes: prefixes&.to_i)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
build_result(rows, complete: complete, recognized: rows.any?, issues: issues, identity: %i[vrf afi_safi peer])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module PaloAlto
|
|
7
|
+
module Parsers
|
|
8
|
+
# 旧版 PAN-OS 邻居回显不标明本地接口或进程。
|
|
9
|
+
class ShowRoutingProtocolOspfNeighbor < CtxChecker::Parsers::Base
|
|
10
|
+
STATES = { "full" => :full, "down" => :down, "init" => :init, "attempt" => :attempt,
|
|
11
|
+
"2-way" => :two_way, "two way" => :two_way, "exchange start" => :exstart, "exstart" => :exstart,
|
|
12
|
+
"exchange" => :exchange, "loading" => :loading }.freeze
|
|
13
|
+
FIELDS = /\A(?:virtual router:|neighbor address:|local address binding:|type:|status:|neighbor router ID:|area id:|neighbor priority:|lifetime remain:|messages pending:|LSA request pending:|options:|hello suppressed:|restart helper status:|restart helper time remaining:|restart helper exit reason:|=+\z)/i
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# 解析 show routing protocol ospf neighbor,保留不完整或不支持的证据。
|
|
18
|
+
def parse_output(text, complete:)
|
|
19
|
+
text = clean(text)
|
|
20
|
+
rows, issues = [], []
|
|
21
|
+
text.split(/(?=^\s*virtual router:)/i).each do |block|
|
|
22
|
+
unless block.match?(/\A\s*virtual router:/i)
|
|
23
|
+
# 文档规定的图例位于首个邻居前,任意前导文本不构成证据。
|
|
24
|
+
lines = block.lines.map(&:strip).reject(&:empty?)
|
|
25
|
+
unless lines.all? { |line| line.match?(/\A(?:=+|Options: 0x80:reserved,.*|N\/P:NSSA option,.*)\z/i) }
|
|
26
|
+
issues << { code: :unrecognized_ospf_block }
|
|
27
|
+
end
|
|
28
|
+
next
|
|
29
|
+
end
|
|
30
|
+
issues << { code: :unrecognized_ospf_field } if block.lines.any? { |line| !line.strip.empty? && !FIELDS.match?(line.strip) }
|
|
31
|
+
if [ "neighbor address", "neighbor router ID", "status" ].any? { |field| block.scan(/^\s*#{Regexp.escape(field)}:/i).size > 1 }
|
|
32
|
+
issues << { code: :duplicate_ospf_field }
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
neighbor = block[/^\s*neighbor address:\s*(\S+)/i, 1]
|
|
36
|
+
router_id = block[/^\s*neighbor router ID:\s*(\S+)/i, 1]
|
|
37
|
+
unless CtxParser::Values.valid_host?(neighbor) && CtxParser::Values.valid_host?(router_id)
|
|
38
|
+
issues << { code: :ospf_neighbor_identity_missing }
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
neighbor = CtxParser::Values.host(neighbor)
|
|
42
|
+
router_id = CtxParser::Values.host(router_id)
|
|
43
|
+
state = STATES.fetch(block[/^\s*status:\s*(.+)$/i, 1]&.strip&.downcase, :unknown)
|
|
44
|
+
issues << { code: :ospf_state_unknown } if state == :unknown
|
|
45
|
+
rows << { vrf: block[/^\s*virtual router:\s*(\S+)/i, 1], process_id: nil,
|
|
46
|
+
area: block[/^\s*area id:\s*(\S+)/i, 1], interface: nil, router_id: router_id, neighbor_address: neighbor,
|
|
47
|
+
state: state, network_type: :unknown, local_role: :unknown, neighbor_role: :unknown }
|
|
48
|
+
end
|
|
49
|
+
build_result(rows, complete: complete, recognized: rows.any?, issues: issues, identity: %i[vrf process_id interface router_id neighbor_address])
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require "ctx_parser/result_set"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module PaloAlto
|
|
8
|
+
module Parsers
|
|
9
|
+
# 特定章节的硬件传感器不能证明未出现的电源或风扇已被覆盖。
|
|
10
|
+
class ShowSystemEnvironmentals < CtxChecker::Parsers::Base
|
|
11
|
+
SECTIONS = { "thermal" => :temperature, "fans" => :environment, "fan tray" => :environment,
|
|
12
|
+
"power" => :power, "power supplies" => :power }.freeze
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 解析 show system environmentals,保留不完整或不支持的证据。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
source = clean(text)
|
|
19
|
+
rows = { temperature: [], environment: [], power: [] }
|
|
20
|
+
issues = rows.transform_values { [] }
|
|
21
|
+
section, header, sections, section_rows = nil, nil, [], 0
|
|
22
|
+
source.each_line.with_index(1) do |raw, number|
|
|
23
|
+
line = raw.strip
|
|
24
|
+
next if line.empty?
|
|
25
|
+
if (match = /\A-+\s*([^-]+?)\s*-+\z/.match(line))
|
|
26
|
+
empty_section(section, section_rows, issues)
|
|
27
|
+
section, header = match[1].downcase, nil
|
|
28
|
+
section_rows = 0
|
|
29
|
+
sections << section
|
|
30
|
+
issues.each_value { |items| items << { code: :unrecognized_environment_section, line: number } } unless SECTIONS.key?(section)
|
|
31
|
+
elsif line.match?(/\ASlot\s+Description\s+Alarm\s+/)
|
|
32
|
+
header = line
|
|
33
|
+
elsif SECTIONS.key?(section) && header
|
|
34
|
+
dimension = SECTIONS.fetch(section)
|
|
35
|
+
if (match = /\A(\S+)\s+(.+?)\s+(True|False)\s+(.+)\z/i.match(line))
|
|
36
|
+
section_rows += 1
|
|
37
|
+
component = "#{match[1]}/#{match[2].strip}"
|
|
38
|
+
alarm = match[3].casecmp?("true")
|
|
39
|
+
status = alarm ? :critical : :ok
|
|
40
|
+
values = match[4].split
|
|
41
|
+
case section
|
|
42
|
+
when "thermal"
|
|
43
|
+
measurements = values.map { |value| numeric(value) }
|
|
44
|
+
expected = header.include?("Min C") && header.include?("Max C") ? 3 : 1
|
|
45
|
+
valid = measurements.size == expected && measurements.none?(&:nil?)
|
|
46
|
+
issues[:temperature] << { code: :invalid_temperature_measurement, component: component } unless valid
|
|
47
|
+
rows[:temperature] << { component: component, value: valid ? measurements.first : nil,
|
|
48
|
+
low_alarm: valid && expected == 3 ? measurements[1] : nil,
|
|
49
|
+
high_alarm: valid && expected == 3 ? measurements[2] : nil, status: status }
|
|
50
|
+
when "power supplies", "fan tray"
|
|
51
|
+
present = values.one? && %w[true false].include?(values.first.downcase) ? values.first.casecmp?("true") : nil
|
|
52
|
+
status = :absent if !alarm && present == false
|
|
53
|
+
issues[dimension] << { code: :invalid_presence, component: component } if present.nil?
|
|
54
|
+
rows[dimension] << if section == "power supplies"
|
|
55
|
+
{ component: component, kind: :power_supply, status: status, present: present }
|
|
56
|
+
else
|
|
57
|
+
{ component: component, kind: :fan, status: status }
|
|
58
|
+
end
|
|
59
|
+
when "fans", "power"
|
|
60
|
+
valid = sensor_values?(section, header, values)
|
|
61
|
+
issues[dimension] << { code: :invalid_environment_measurement, component: component } unless valid
|
|
62
|
+
status = :unknown if !valid && !alarm
|
|
63
|
+
if valid && limits_invalid?(section, header, values)
|
|
64
|
+
issues[dimension] << { code: :invalid_environment_thresholds, component: component }
|
|
65
|
+
status = :unknown unless alarm
|
|
66
|
+
elsif valid && outside_limits?(section, header, values)
|
|
67
|
+
issues[dimension] << { code: :conflicting_environment_state, component: component } unless alarm
|
|
68
|
+
status = :failed unless alarm
|
|
69
|
+
end
|
|
70
|
+
if section == "fans" && header.match?(/Operational\s*\z/) && values == [ "False" ]
|
|
71
|
+
issues[dimension] << { code: :conflicting_environment_state, component: component } unless alarm
|
|
72
|
+
status = :failed
|
|
73
|
+
end
|
|
74
|
+
if section == "power"
|
|
75
|
+
rows[:power] << { component: component, kind: :power_rail, status: status }
|
|
76
|
+
else
|
|
77
|
+
rows[:environment] << { component: component, kind: :fan, status: status }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
issues[dimension] << { code: :unrecognized_environment_row, line: number }
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
issues.each_value { |items| items << { code: :unrecognized_environment_row, line: number } }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
empty_section(section, section_rows, issues)
|
|
88
|
+
if rows[:power].any? && !sections.include?("power supplies")
|
|
89
|
+
issues[:power] << { code: :power_supply_status_missing }
|
|
90
|
+
end
|
|
91
|
+
CtxParser::ResultSet.new(results: rows.to_h do |dimension, values|
|
|
92
|
+
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
93
|
+
[ dimension, build_result(values, complete: complete, recognized: values.any?, issues: issues.fetch(dimension), identity: identity) ]
|
|
94
|
+
end)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 解析支持的数值字段,并拒绝格式错误的测量值。
|
|
98
|
+
def numeric(value)
|
|
99
|
+
return unless /\A-?\d+(?:\.\d+)?\z/.match?(value)
|
|
100
|
+
number = Float(value)
|
|
101
|
+
number if number.finite?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# 表达明确的空章节,不虚构组件记录。
|
|
105
|
+
def empty_section(section, count, issues)
|
|
106
|
+
return unless SECTIONS.key?(section) && count.zero?
|
|
107
|
+
issues.fetch(SECTIONS.fetch(section)) << { code: :empty_environment_section, section: section }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# 检查传感器行是否包含可用的数值测量。
|
|
111
|
+
def sensor_values?(section, header, values)
|
|
112
|
+
if section == "fans" && header.match?(/Operational\s*\z/)
|
|
113
|
+
values.one? && %w[True False].include?(values.first)
|
|
114
|
+
else
|
|
115
|
+
expected = if section == "fans"
|
|
116
|
+
header.include?("Min RPM") ? 2 : 1
|
|
117
|
+
else
|
|
118
|
+
header.include?("Min V") && header.include?("Max V") ? 3 : 1
|
|
119
|
+
end
|
|
120
|
+
values.size == expected && values.all? { |value| (number = numeric(value)) && (section != "fans" || number >= 0) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 识别矛盾或格式错误的传感器阈值边界。
|
|
125
|
+
def limits_invalid?(section, header, values)
|
|
126
|
+
section == "power" && header.include?("Min V") && header.include?("Max V") && numeric(values[1]) > numeric(values[2])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# 过期的 False 告警标志不能抹除实际观察到的设备阈值违规。
|
|
130
|
+
def outside_limits?(section, header, values)
|
|
131
|
+
if section == "fans" && header.include?("Min RPM")
|
|
132
|
+
numeric(values[0]) < numeric(values[1])
|
|
133
|
+
elsif section == "power" && header.include?("Min V") && header.include?("Max V")
|
|
134
|
+
!numeric(values[0]).between?(numeric(values[1]), numeric(values[2]))
|
|
135
|
+
else
|
|
136
|
+
false
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require "ctx_parser/result_set"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module PaloAlto
|
|
8
|
+
module Parsers
|
|
9
|
+
# PAN top 描述管理平面,并包含可回收内存。
|
|
10
|
+
class ShowSystemResources < CtxChecker::Parsers::Base
|
|
11
|
+
UNITS = { "KiB" => 1024, "MiB" => 1024**2, "GiB" => 1024**3, "TiB" => 1024**4 }.freeze
|
|
12
|
+
NUMBER = /\d+(?:\.\d+)?/
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 解析 show system resources,保留不完整或不支持的证据。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
source = clean(text)
|
|
19
|
+
cpus, memories, cpu_issues, memory_issues, shared_issues = [], [], [], [], []
|
|
20
|
+
swap_lines = source.lines.grep(/\A\s*(?:(?:KiB|MiB|GiB|TiB)\s+)?Swap\s*:/)
|
|
21
|
+
process_table = false
|
|
22
|
+
source.each_line.with_index(1) do |raw, number|
|
|
23
|
+
line = raw.strip
|
|
24
|
+
if (match = /\A%?Cpu(?:\(s\)|(\d+))\s*:\s*(.*)\z/i.match(line))
|
|
25
|
+
cpus << cpu(match, cpu_issues)
|
|
26
|
+
elsif line.match?(/\A(?:(?:KiB|MiB|GiB|TiB)\s+)?Mem\s*:/)
|
|
27
|
+
memories << memory(line, swap_lines, memory_issues)
|
|
28
|
+
elsif line.match?(/\APID\s+USER\s+/)
|
|
29
|
+
process_table = true
|
|
30
|
+
elsif line.empty? || line.match?(/\A(?:top - |Tasks:|(?:(?:KiB|MiB|GiB|TiB)\s+)?Swap\s*:)/) || process_table && line.match?(/\A\d+\s+/)
|
|
31
|
+
next
|
|
32
|
+
else
|
|
33
|
+
shared_issues << { code: :unrecognized_resource_row, line: number }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
CtxParser::ResultSet.new(results: {
|
|
37
|
+
cpu: build_result(cpus, complete: complete, recognized: cpus.any?, issues: shared_issues + cpu_issues,
|
|
38
|
+
identity: %i[component scope window_seconds]),
|
|
39
|
+
memory: build_result(memories, complete: complete, recognized: memories.any?, issues: shared_issues + memory_issues,
|
|
40
|
+
identity: %i[component scope])
|
|
41
|
+
})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 归一化 CPU 利用率,并保留其采样周期。
|
|
45
|
+
def cpu(match, issues)
|
|
46
|
+
parts = match[2].split(",").map { |part| /\A\s*(#{NUMBER})\s*%?\s*(us|sy|ni|id|wa|hi|si|st)\s*\z/.match(part) }
|
|
47
|
+
values = parts.compact.to_h { |part| [ part[2], Float(part[1]) ] }
|
|
48
|
+
valid = parts.size == 8 && parts.all? && values.size == 8 && values.values.all? { |value| value.finite? && value.between?(0, 100) } &&
|
|
49
|
+
(values.values.sum - 100).abs <= 1
|
|
50
|
+
issues << { code: :invalid_cpu_measurement } unless valid
|
|
51
|
+
{ component: match[1] ? "management/cpu#{match[1]}" : "management", scope: "management_plane", window_seconds: nil,
|
|
52
|
+
utilization: valid ? (100 - values.fetch("id")).round(6) : nil, interrupt_utilization: valid ? (values.fetch("hi") + values.fetch("si")).round(6) : nil }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 仅根据可用内存,或文档明确的旧版缓存调整方式计算利用率。
|
|
56
|
+
def memory(line, swap_lines, issues)
|
|
57
|
+
header = /\A(?:(KiB|MiB|GiB|TiB)\s+)?Mem\s*:\s*(.*)\z/.match(line)
|
|
58
|
+
unit = header[1] ? UNITS.fetch(header[1]) : 1024
|
|
59
|
+
pairs = header[2].split(",").map { |part| /\A\s*(#{NUMBER})(k?)\s+(total|used|free|buffers|buff\/cache)\s*\z/.match(part) }
|
|
60
|
+
values = pairs.compact.to_h { |pair| [ pair[3], bytes(pair[1], unit) ] }
|
|
61
|
+
available = swap_lines.flat_map { |swap| swap.scan(/(?:\A|[\s,.])(#{NUMBER})\s+avail Mem\s*\z/).flatten }
|
|
62
|
+
cached = swap_lines.flat_map { |swap| swap.scan(/(?:\A|[\s,])(#{NUMBER})k\s+cached\s*\z/).flatten }
|
|
63
|
+
row = { component: "management", scope: "management_plane", total_bytes: values["total"], free_bytes: values["free"],
|
|
64
|
+
used_bytes: values["used"], available_bytes: nil, utilization: nil, basis: :unknown }
|
|
65
|
+
valid_units = pairs.all? { |pair| pair && (header[1] ? pair[2].empty? : pair[2] == "k") }
|
|
66
|
+
valid = valid_units && pairs.size == values.size && values.values.none?(&:nil?) && row[:total_bytes]&.positive? &&
|
|
67
|
+
values.values.all? { |value| value <= row[:total_bytes] }
|
|
68
|
+
if valid && available.one? && swap_lines.one?
|
|
69
|
+
swap_unit = /\A\s*(KiB|MiB|GiB|TiB)\s+Swap/.match(swap_lines.first)&.[](1)
|
|
70
|
+
row[:available_bytes] = bytes(available.first, swap_unit ? UNITS.fetch(swap_unit) : 1024)
|
|
71
|
+
valid &&= row[:available_bytes] && row[:available_bytes] <= row[:total_bytes]
|
|
72
|
+
row[:used_bytes] = row[:total_bytes] - row[:available_bytes] if valid
|
|
73
|
+
row[:basis] = :available if valid
|
|
74
|
+
elsif valid && header[1].nil? && cached.one? && swap_lines.one? && values["buffers"] && values["used"]
|
|
75
|
+
cache_bytes = bytes(cached.first, 1024)
|
|
76
|
+
adjusted = cache_bytes && values["used"] - values["buffers"] - cache_bytes
|
|
77
|
+
valid &&= adjusted && adjusted >= 0
|
|
78
|
+
row[:used_bytes], row[:basis] = adjusted, :used_minus_cache if valid
|
|
79
|
+
row[:available_bytes] = row[:total_bytes] - adjusted if valid
|
|
80
|
+
else
|
|
81
|
+
valid = false
|
|
82
|
+
end
|
|
83
|
+
if valid
|
|
84
|
+
row[:utilization] = (100.0 * row[:used_bytes] / row[:total_bytes]).round(6)
|
|
85
|
+
else
|
|
86
|
+
issues << { code: :memory_available_unknown, component: "management" }
|
|
87
|
+
row[:basis] = :unknown
|
|
88
|
+
row[:available_bytes] = nil
|
|
89
|
+
row[:used_bytes] = nil if row[:used_bytes] && row[:total_bytes] && row[:used_bytes] > row[:total_bytes]
|
|
90
|
+
end
|
|
91
|
+
row
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 将支持的内存单位转换为字节。
|
|
95
|
+
def bytes(value, multiplier)
|
|
96
|
+
number = Float(value) * multiplier
|
|
97
|
+
number.round if number.finite? && number >= 0
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module PaloAlto
|
|
7
|
+
module Parsers
|
|
8
|
+
# 读取当前告警布尔值,不执行 PAN 类 Python 状态文本。
|
|
9
|
+
class ShowSystemStateAlarms < CtxChecker::Parsers::Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# 解析 show system state alarms,保留不完整或不支持的证据。
|
|
13
|
+
def parse_output(text, complete:)
|
|
14
|
+
source = clean(text)
|
|
15
|
+
rows, issues = [], []
|
|
16
|
+
source.each_line.with_index(1) do |raw, number|
|
|
17
|
+
line = raw.strip
|
|
18
|
+
# 配置阈值和复制的 HA 对端状态不属于本地当前告警。
|
|
19
|
+
next if line.empty? || line.match?(/\A(?:cfg\.|peer\.)/)
|
|
20
|
+
if line.match?(/\Achassis\.alarm:\s*\{\s*\}\z/)
|
|
21
|
+
rows << { component: "chassis", alarm_id: "summary", active: false, count: 0, severity: :unknown }
|
|
22
|
+
elsif (match = /\A(ha\.(?:runtime\.device|app\.local)\.alarm):\s*(True|False)\z/i.match(line))
|
|
23
|
+
rows << alarm(match[1], match[2])
|
|
24
|
+
elsif (match = /\A(env\.[a-z0-9_.-]+):\s*\{(.+)\}\z/i.match(line))
|
|
25
|
+
states = match[2].scan(/(?:\A|,)\s*'alarm':\s*(True|False)\s*(?=,|\z)/i).flatten
|
|
26
|
+
if states.one?
|
|
27
|
+
rows << alarm(match[1], states.first)
|
|
28
|
+
else
|
|
29
|
+
issues << { code: :ambiguous_alarm_state, line: number }
|
|
30
|
+
end
|
|
31
|
+
elsif (match = /\A((?:chassis(?:\.s\d+)?|hw\.(?:slot\d+|s\d+\.mp))\.leds):\s*\{(.+)\}\z/i.match(line))
|
|
32
|
+
states = match[2].scan(/(?:\A|,)\s*'alarm':\s*(Off|Red|Green|Yellow|Amber)\s*(?=,|\z)/i).flatten
|
|
33
|
+
if states.one?
|
|
34
|
+
value = states.first.downcase
|
|
35
|
+
rows << { component: match[1], alarm_id: "alarm_led", active: !%w[off green].include?(value),
|
|
36
|
+
severity: :unknown, count: nil }
|
|
37
|
+
else
|
|
38
|
+
issues << { code: :ambiguous_alarm_state, line: number }
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
issues << { code: :unrecognized_alarm_state, line: number }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
build_result(rows, complete: complete, recognized: rows.any?, issues: issues, identity: %i[component alarm_id])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 归一化单条当前告警及其严重级别。
|
|
48
|
+
def alarm(component, value)
|
|
49
|
+
{ component: component, alarm_id: "alarm", active: value.casecmp?("true"), severity: :unknown, count: nil }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|