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,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
require "ctx_parser/result_set"
|
|
6
|
+
|
|
7
|
+
module CtxChecker
|
|
8
|
+
module CiscoIos
|
|
9
|
+
module Parsers
|
|
10
|
+
# 同一接口详细回显分别提供状态与计数器观测。
|
|
11
|
+
class ShowInterfaces < CtxChecker::Parsers::Base
|
|
12
|
+
PLATFORM = :cisco_ios
|
|
13
|
+
HEADER = /\A(\S+) is (administratively down|up|down|reset|deleted)(?:, line protocol is (up|down)(?: \([^)]*\))?)?(?:\s*\([^)]*\))?(?:\s*,\s*Autostate (?:Enabled|Disabled))?\s*\z/i
|
|
14
|
+
COUNTERS = { in_errors: /(?<!\S)(\d+) input errors?\b/i, out_errors: /(?<!\S)(\d+) output errors?\b/i,
|
|
15
|
+
crc_errors: /(?<!\S)(\d+) CRC(?:\/FCS)?\b/i, in_discards: /(?<!\S)(\d+) input discards?\b/i,
|
|
16
|
+
out_discards: /(?<!\S)(\d+) output discards?\b/i }.freeze
|
|
17
|
+
COUNTER_LABELS = { in_errors: /\binput errors?\b/i, out_errors: /\boutput errors?\b/i,
|
|
18
|
+
crc_errors: /\bCRC(?:\/FCS)?\b/i, in_discards: /\binput discards?\b/i, out_discards: /\boutput discards?\b/i }.freeze
|
|
19
|
+
AUXILIARY = /\A(?:Hardware[: ]|Description:|Internet address|MTU |reliability |Encapsulation |Keepalive |Full-duplex|Half-duplex|full-duplex|half-duplex|Auto-duplex|auto-duplex|Input flow-control|Output flow-control|ARP type:|Last input |Last clearing |Input queue:|Output queue:|Queueing strategy:|[\d ]+(?:second|minute) (?:input|output) rate|[\d ]+(?:packets input|packets output|bytes|no buffer|broadcasts|multicasts|runts|giants|watchdog|output buffer|lost carrier|unknown protocol drops|interface resets|carrier transitions|collisions|babbles|underruns)|Received |Output feature|L2 Switched:|L3 in Switched:|L3 out Switched:|Port mode is |Beacon is |Auto-Negotiation is |FEC mode is |Flowcontrol|Switchport monitor|EtherType is |Members in this channel:|Last link flapped |Last link change |Last input change |[\d ]+interface resets|Load-Interval |Input rate |Output rate |RX\z|TX\z|[\d ]+(?:unicast packets|input packets|output packets|jumbo packets|Rx pause|Tx pause|storm suppression)|Tunnel |tunnel |Transport |Destination |Source |DTR is |[\d ]+packets,|BW |Mode is |IP address is |Port-channel is |admin state is |[\d ]+bad etype drop|Last clearing of)/i
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# 分别跟踪覆盖率;有效状态头不能证明计数器已采集。
|
|
24
|
+
def parse_output(text, complete:)
|
|
25
|
+
text = clean(text)
|
|
26
|
+
interfaces, counters, state_issues, counter_issues = [], [], [], []
|
|
27
|
+
current = nil
|
|
28
|
+
text.each_line.with_index(1) do |source, number|
|
|
29
|
+
line = source.strip
|
|
30
|
+
next if line.empty?
|
|
31
|
+
|
|
32
|
+
if (match = interface_header(line))
|
|
33
|
+
current = interface_record(match)
|
|
34
|
+
interfaces << current
|
|
35
|
+
elsif (match = /\A(\S+) is /.match(line)) && CtxChecker::Parsers::InterfaceNames.kind(normalize_name(match[1])) != :unknown
|
|
36
|
+
current = nil
|
|
37
|
+
state_issues << { code: :unparsed_interface_header, line: number }
|
|
38
|
+
elsif current && (match = /\Aadmin state is (up|down)\b/i.match(line))
|
|
39
|
+
current[:admin_status] = match[1].downcase.to_sym
|
|
40
|
+
elsif current && (match = /\ALine protocol is (up|down)\b/i.match(line))
|
|
41
|
+
current[:protocol_status] = match[1].downcase.to_sym
|
|
42
|
+
elsif current && (match = /\ALast clearing of "show interfaces?" counters\s+(.+)\z/i.match(line))
|
|
43
|
+
# 相对清零时长不能证明两个采样间的计数器连续性。
|
|
44
|
+
current[:discontinuity] = nil
|
|
45
|
+
elsif current && /\ADescription:/i.match?(line)
|
|
46
|
+
next
|
|
47
|
+
elsif current && counter_line?(line)
|
|
48
|
+
found = counter_patterns.flat_map do |metric, pattern|
|
|
49
|
+
source = counter_source(line, metric)
|
|
50
|
+
values = source.scan(pattern).flatten
|
|
51
|
+
if values.size != source.scan(counter_labels.fetch(metric)).size
|
|
52
|
+
counter_issues << { code: :invalid_counter_value, metric: metric, line: number }
|
|
53
|
+
end
|
|
54
|
+
values.map { |value| { interface: current[:name], metric: metric, value: Integer(value, 10), discontinuity: nil } }
|
|
55
|
+
end
|
|
56
|
+
output_drops = line.scan(/\bTotal output drops:\s*(\d+)(?=\s*(?:;|\z))/i).flatten
|
|
57
|
+
output_drops.each do |value|
|
|
58
|
+
# IOS-XE 此处可能显示字节数或包数,取决于平台配置。
|
|
59
|
+
found << { interface: current[:name], metric: :out_discards, value: Integer(value, 10), discontinuity: nil,
|
|
60
|
+
unit: :unknown, scope: :queue }
|
|
61
|
+
end
|
|
62
|
+
if output_drops.size != line.scan(/\bTotal output drops:/i).size
|
|
63
|
+
counter_issues << { code: :invalid_counter_value, metric: :out_discards, line: number }
|
|
64
|
+
end
|
|
65
|
+
if (match = /\AInput queue:\s*\d+\/\d+\/(\d+)(?:\/\d+)?\s+\(size\/max\/drops(?:\/flushes)?\)/i.match(line))
|
|
66
|
+
found << { interface: current[:name], metric: :in_discards, value: Integer(match[1], 10), discontinuity: nil,
|
|
67
|
+
unit: :packets, scope: :queue }
|
|
68
|
+
elsif line.match?(/\AInput queue:.*drops/i)
|
|
69
|
+
counter_issues << { code: :invalid_counter_value, metric: :in_discards, line: number }
|
|
70
|
+
end
|
|
71
|
+
counters.concat(found)
|
|
72
|
+
counter_issues << { code: :unparsed_counter, line: number } if found.empty?
|
|
73
|
+
elsif current && interface_detail(current, line)
|
|
74
|
+
next
|
|
75
|
+
elsif current && auxiliary?(line)
|
|
76
|
+
next
|
|
77
|
+
else
|
|
78
|
+
state_issues << { code: :unparsed_row, line: number }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
discontinuities = interfaces.to_h { |record| [ record[:name], record.delete(:discontinuity) ] }
|
|
82
|
+
counters.each { |record| record[:discontinuity] = discontinuities[record[:interface]] }
|
|
83
|
+
counters_by_interface = counters.group_by { |counter| counter[:interface] }
|
|
84
|
+
interfaces.select { |record| record[:kind] == :physical }.each do |record|
|
|
85
|
+
counters.concat(complete_counter_coverage(record, counters_by_interface.fetch(record[:name], []), counter_issues))
|
|
86
|
+
end
|
|
87
|
+
CtxParser::ResultSet.new(results: {
|
|
88
|
+
interfaces: build_result(interfaces, complete: complete, recognized: interfaces.any?, issues: state_issues, identity: [ :name ]),
|
|
89
|
+
counters: build_result(counters, complete: complete, recognized: counters.any?, issues: state_issues + counter_issues,
|
|
90
|
+
identity: %i[interface metric])
|
|
91
|
+
})
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 按计数器标签匹配,不误取总包数,并保留格式错误的指标行。
|
|
95
|
+
def counter_line?(line)
|
|
96
|
+
line.match?(/\b(?:input errors?|output errors?|CRC(?:\/FCS)?|input discards?|output discards?|Total output drops)\b/i) ||
|
|
97
|
+
line.match?(/\AInput queue:.*drops/i)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 将接口别名归一化为平台标准身份。
|
|
101
|
+
def normalize_name(name) = CtxChecker::Parsers::InterfaceNames.normalize(name, platform: self.class::PLATFORM)
|
|
102
|
+
|
|
103
|
+
# 平台语法覆盖独立于公共计数器提取逻辑。
|
|
104
|
+
def interface_header(line) = HEADER.match(line)
|
|
105
|
+
# 将平台特有的详细行留给子类扩展点处理。
|
|
106
|
+
def interface_detail(_record, _line) = false
|
|
107
|
+
# 识别说明性回显,不将其作为数据记录。
|
|
108
|
+
def auxiliary?(line)
|
|
109
|
+
AUXILIARY.match?(line) || /\AOutput \d+ broadcasts \(\d+ (?:IP )?multicasts\)\z/i.match?(line) ||
|
|
110
|
+
/\AUnknown, Unknown, link type is \S+, media type is \S+\z/i.match?(line) || /\AFec is \S+\z/i.match?(line)
|
|
111
|
+
end
|
|
112
|
+
# 声明当前平台支持的接口错误与丢弃计数模式。
|
|
113
|
+
def counter_patterns = COUNTERS
|
|
114
|
+
# 将平台计数器标签映射到归一化指标名称。
|
|
115
|
+
def counter_labels = COUNTER_LABELS
|
|
116
|
+
# 标记这些计数测量来自哪个命令视图。
|
|
117
|
+
def counter_source(line, _metric) = line
|
|
118
|
+
|
|
119
|
+
# 物理端口健康需要明确的错误包与 CRC 测量证据。
|
|
120
|
+
def complete_counter_coverage(record, counters, issues)
|
|
121
|
+
missing = %i[in_errors out_errors crc_errors] - counters.map { |counter| counter[:metric] }
|
|
122
|
+
issues << { code: :counter_fields_missing, interface: record[:name], metrics: missing } if missing.any?
|
|
123
|
+
[]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# 复用接口头字段及子接口身份,不虚构协议状态。
|
|
127
|
+
def interface_record(match)
|
|
128
|
+
name = normalize_name(match[1])
|
|
129
|
+
record = { name: name, kind: CtxChecker::Parsers::InterfaceNames.kind(name),
|
|
130
|
+
admin_status: match[2].casecmp?("administratively down") ? :down : :unknown,
|
|
131
|
+
oper_status: match[2].casecmp?("up") ? :up : :down,
|
|
132
|
+
protocol_status: match[3]&.downcase&.to_sym || :unknown, parent: nil }
|
|
133
|
+
record[:admin_status] = :up if self.class::PLATFORM == :cisco_ios && record[:admin_status] != :down
|
|
134
|
+
record[:parent] = name.split(".", 2).first if record[:kind] == :subinterface
|
|
135
|
+
record
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module CiscoIos
|
|
8
|
+
module Parsers
|
|
9
|
+
# IOS DOM 按指标分表展示端口,其阈值顺序不同于 NX-OS。
|
|
10
|
+
class ShowInterfacesTransceiverDetail < CtxChecker::Parsers::Base
|
|
11
|
+
NUMBER = /(?:[+-]?\d+(?:\.\d+)?|N\/A|NA|--)/i
|
|
12
|
+
MARKERS = { "++" => :high_alarm, "+" => :high_warning, "--" => :low_alarm, "-" => :low_warning }.freeze
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 解析 show interfaces transceiver detail,保留不完整或不支持的证据。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
text = clean(text)
|
|
19
|
+
rows, issues, ports = [], [], []
|
|
20
|
+
metric, header, lane_column = nil, false, false
|
|
21
|
+
text.each_line.with_index(1) do |source, number|
|
|
22
|
+
line = source.strip
|
|
23
|
+
next if line.empty? || line.match?(/\A[-= ]+\z/)
|
|
24
|
+
|
|
25
|
+
if line.match?(/\bTransmit Power\b/i)
|
|
26
|
+
metric, header = :tx_power, false
|
|
27
|
+
elsif line.match?(/\bReceive Power\b/i)
|
|
28
|
+
metric, header = :rx_power, false
|
|
29
|
+
elsif line.match?(/\A(?:Temperature|Voltage|Current)\s+Threshold/i)
|
|
30
|
+
metric, header = :other, false
|
|
31
|
+
elsif line.match?(/\APort\s+(?:Lane\s+)?\(/i)
|
|
32
|
+
header = true
|
|
33
|
+
lane_column = line.match?(/\APort\s+Lane\s/i)
|
|
34
|
+
elsif interface_row?(line)
|
|
35
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(line.split.first, platform: :cisco_ios)
|
|
36
|
+
ports << name
|
|
37
|
+
if metric && header
|
|
38
|
+
next if metric == :other
|
|
39
|
+
|
|
40
|
+
row = measurement(line, metric, lane_column)
|
|
41
|
+
row ? rows << row : issues << { code: :unparsed_optical_measurement, line: number }
|
|
42
|
+
else
|
|
43
|
+
issues << { code: :missing_optical_header, line: number }
|
|
44
|
+
end
|
|
45
|
+
elsif auxiliary?(line)
|
|
46
|
+
next
|
|
47
|
+
else
|
|
48
|
+
issues << { code: :unparsed_row, line: number }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
identities = rows.map { |row| row.values_at(:interface, :lane) }
|
|
52
|
+
identities.concat((ports.uniq - rows.map { |row| row[:interface] }).map { |name| [ name, nil ] })
|
|
53
|
+
identities.uniq.each do |name, lane|
|
|
54
|
+
%i[tx_power rx_power].each do |expected|
|
|
55
|
+
next if rows.any? { |row| row.values_at(:interface, :lane, :metric) == [ name, lane, expected ] }
|
|
56
|
+
|
|
57
|
+
issues << { code: :optical_measurement_missing, interface: name, lane: lane, metric: expected }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
build_result(rows, complete: complete, recognized: rows.any?, issues: issues, identity: %i[interface lane metric])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 解析支持的传感器测量值及回显单位。
|
|
64
|
+
def measurement(line, metric, lane_column)
|
|
65
|
+
# 括号内 A2D 读数属于独立校准证据,不作为阈值。
|
|
66
|
+
normalized = line.gsub(/\(\s*#{NUMBER}\s*\)/i, "")
|
|
67
|
+
columns = normalized.split
|
|
68
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(columns.shift, platform: :cisco_ios)
|
|
69
|
+
lane = lane_column ? columns.shift : nil
|
|
70
|
+
lane = nil if lane && /\AN\/?A\z/i.match?(lane)
|
|
71
|
+
return if lane && !lane.match?(/\A\d+\z/)
|
|
72
|
+
|
|
73
|
+
current = columns.shift
|
|
74
|
+
marker = columns.shift if MARKERS.key?(columns.first)
|
|
75
|
+
return unless current&.match?(/\A#{NUMBER}\z/) && columns.size == 4 && columns.all? { |value| value.match?(/\A#{NUMBER}\z/) }
|
|
76
|
+
|
|
77
|
+
high_alarm, high_warning, low_warning, low_alarm = columns.map { |value| decimal(value) }
|
|
78
|
+
{ interface: name, lane: lane && Integer(lane, 10), metric: metric, value: decimal(current), unit: :dbm,
|
|
79
|
+
low_alarm: low_alarm, low_warning: low_warning, high_warning: high_warning, high_alarm: high_alarm,
|
|
80
|
+
alarm: marker ? MARKERS.fetch(marker) : :normal, present: true, dom_supported: true }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 区分接口数据行与表格标题。
|
|
84
|
+
def interface_row?(line)
|
|
85
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(line.split.first, platform: :cisco_ios)
|
|
86
|
+
CtxChecker::Parsers::InterfaceNames.kind(name) == :physical
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 解析有限小数,不将占位符转换为零。
|
|
90
|
+
def decimal(value)
|
|
91
|
+
return unless value.match?(/\A[+-]?\d+(?:\.\d+)?\z/)
|
|
92
|
+
|
|
93
|
+
parsed = Float(value)
|
|
94
|
+
return parsed if parsed.finite?
|
|
95
|
+
|
|
96
|
+
@issues << { code: :invalid_optical_value }
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 识别说明性回显,不将其作为数据记录。
|
|
101
|
+
def auxiliary?(line)
|
|
102
|
+
line.match?(/\A(?:mA: |\+\+\s*:|A2D readouts |The threshold values |(?:Optical\s+)?High Alarm\s+High Warn\s+Low Warn\s+Low Alarm\z|Optical\z|ITU Channel |Transceiver is |Transceiver monitoring is |Wavelength |(?:Temperature|Voltage|Current)\s+Threshold)/i)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require "ctx_parser/values"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module CiscoIos
|
|
8
|
+
module Parsers
|
|
9
|
+
# 仅以 BGP 汇总表声明的最后一列作为权威会话状态。
|
|
10
|
+
class ShowIpBgpSummary < CtxChecker::Parsers::Base
|
|
11
|
+
ADDRESS_FAMILY = "ipv4_unicast".freeze
|
|
12
|
+
HEADER = /\ANeighbor\s+V\s+AS\s+MsgRcvd\s+MsgSent\s+TblVer\s+InQ\s+OutQ\s+Up\/Down\s+State\/PfxRcd\z/i
|
|
13
|
+
STATES = { "idle" => :idle, "active" => :active, "connect" => :connect, "opensent" => :opensent,
|
|
14
|
+
"openconfirm" => :openconfirm, "established" => :established, "shutdown" => :shutdown,
|
|
15
|
+
"idle (admin)" => :shutdown }.freeze
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# 使用作用域与行身份,避免不同地址族或 VRF 相互覆盖。
|
|
20
|
+
def parse_output(text, complete:)
|
|
21
|
+
text = clean(text).gsub(/State\/\s*\n\s*PfxRcd/, "State/PfxRcd")
|
|
22
|
+
rows, issues = [], []
|
|
23
|
+
scope = { vrf: "default", afi_safi: self.class::ADDRESS_FAMILY }
|
|
24
|
+
sections = []
|
|
25
|
+
section = { header: false, empty: false, count: 0, scope_fields: [], scope: scope }
|
|
26
|
+
sections << section
|
|
27
|
+
pending = nil
|
|
28
|
+
text.each_line.with_index(1) do |source, number|
|
|
29
|
+
line = source.strip
|
|
30
|
+
next if line.empty? || line.match?(/\A[-= ]+\z/)
|
|
31
|
+
|
|
32
|
+
if HEADER.match?(line)
|
|
33
|
+
section[:header] = true
|
|
34
|
+
elsif (change = parse_scope(line, scope, issues, number))
|
|
35
|
+
issues << { code: :truncated_peer } if pending
|
|
36
|
+
pending = nil
|
|
37
|
+
next_scope, declared_fields = change
|
|
38
|
+
if section[:header] || section[:empty] || section[:count].positive? || (section[:scope_fields] & declared_fields).any?
|
|
39
|
+
section = { header: false, empty: false, count: 0, scope_fields: [] }
|
|
40
|
+
sections << section
|
|
41
|
+
end
|
|
42
|
+
section[:scope_fields] |= declared_fields
|
|
43
|
+
scope = next_scope
|
|
44
|
+
section[:scope] = scope
|
|
45
|
+
elsif line.match?(/\A(?:No (?:BGP )?neighbors(?: found)?\.?|0 (?:BGP )?neighbors)\z/i)
|
|
46
|
+
section[:empty] = true
|
|
47
|
+
elsif parse_statistics(line, section, scope, issues, number)
|
|
48
|
+
next
|
|
49
|
+
elsif CtxParser::Values.valid_host?(line)
|
|
50
|
+
issues << { code: :unparsed_row, line: number - 1 } if pending
|
|
51
|
+
pending = line
|
|
52
|
+
elsif peer_row?(line) || (pending && line.match?(/\A4\s/))
|
|
53
|
+
line = "#{pending} #{line}" if pending
|
|
54
|
+
pending = nil
|
|
55
|
+
row = parse_peer(line, scope)
|
|
56
|
+
if row
|
|
57
|
+
rows << row
|
|
58
|
+
section[:count] += 1
|
|
59
|
+
issues << { code: :missing_neighbor_header, line: number } unless section[:header]
|
|
60
|
+
else
|
|
61
|
+
issues << { code: :unparsed_peer, line: number }
|
|
62
|
+
end
|
|
63
|
+
elsif auxiliary?(line)
|
|
64
|
+
next
|
|
65
|
+
else
|
|
66
|
+
issues << { code: :unparsed_row, line: number }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
issues << { code: :truncated_peer } if pending
|
|
70
|
+
sections.each { |entry| validate_section(entry, issues) }
|
|
71
|
+
recognized = sections.any? { |entry| section_empty?(entry) || entry[:header] && entry[:count].positive? }
|
|
72
|
+
scopes = sections.filter_map do |entry|
|
|
73
|
+
entry[:scope] if section_empty?(entry) || entry[:header] && entry[:count].positive?
|
|
74
|
+
end
|
|
75
|
+
build_result(rows, complete: complete, recognized: recognized, issues: issues,
|
|
76
|
+
identity: %i[vrf afi_safi peer], covered_scopes: scopes)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 识别协议对等体记录,排除摘要和表头。
|
|
80
|
+
def peer_row?(line) = CtxParser::Values.valid_host?(line.split.first.to_s)
|
|
81
|
+
|
|
82
|
+
# 解析单个路由作用域,不合并无关的 VRF 证据。
|
|
83
|
+
def parse_scope(line, scope, issues, number)
|
|
84
|
+
if (match = /\ABGP summary information for VRF (\S+), address family (.+)\z/i.match(line))
|
|
85
|
+
scope = scope.merge(vrf: match[1])
|
|
86
|
+
family = match[2]
|
|
87
|
+
declared_fields = %i[vrf afi_safi]
|
|
88
|
+
elsif (match = /\A(?:For address family:|Address family:)\s*(.+)\z/i.match(line))
|
|
89
|
+
family = match[1]
|
|
90
|
+
declared_fields = [ :afi_safi ]
|
|
91
|
+
elsif (match = /\A(?:BGP VRF|VRF:)\s+(\S+?)(?:,.*)?\z/i.match(line))
|
|
92
|
+
return [ scope.merge(vrf: match[1]), [ :vrf ] ]
|
|
93
|
+
elsif line.match?(/\A(?:BGP summary information\b|BGP VRF\b|VRF:|For address family:|Address family:)/i)
|
|
94
|
+
issues << { code: :invalid_protocol_scope, line: number }
|
|
95
|
+
return [ { vrf: nil, afi_safi: nil }, %i[vrf afi_safi] ]
|
|
96
|
+
else
|
|
97
|
+
return
|
|
98
|
+
end
|
|
99
|
+
normalized = address_family(family)
|
|
100
|
+
issues << { code: :unknown_address_family, line: number } unless normalized
|
|
101
|
+
[ scope.merge(afi_safi: normalized), declared_fields ]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# 允许平台扩展读取统计信息,但不放宽对等体行校验。
|
|
105
|
+
def parse_statistics(_line, _section, _scope, _issues, _number) = false
|
|
106
|
+
|
|
107
|
+
# 未采集到邻居记录时,仅明确空表声明足以证明为空。
|
|
108
|
+
def section_empty?(section) = section[:empty]
|
|
109
|
+
|
|
110
|
+
# 每张表都必须独立核对记录行与空表声明。
|
|
111
|
+
def validate_section(section, issues)
|
|
112
|
+
issues << { code: :contradictory_empty } if section_empty?(section) && section[:count].positive?
|
|
113
|
+
if (section[:header] || section[:scope_fields].any?) && section[:count].zero? && !section_empty?(section)
|
|
114
|
+
issues << { code: :neighbor_count_unavailable }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 仅归一化支持的地址族;未知作用域保留为 nil。
|
|
119
|
+
def address_family(value)
|
|
120
|
+
{ "ipv4 unicast" => "ipv4_unicast", "ipv6 unicast" => "ipv6_unicast",
|
|
121
|
+
"l2vpn evpn" => "l2vpn_evpn" }[value.downcase.split.join(" ")]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 归一化单个路由对等体及其协议状态。
|
|
125
|
+
def parse_peer(line, scope)
|
|
126
|
+
match = /\A(\S+)\s+4\s+(\d+(?:\.\d+)?)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\S+\s+(.+)\z/.match(line)
|
|
127
|
+
return unless match && CtxParser::Values.valid_host?(match[1])
|
|
128
|
+
|
|
129
|
+
remote_as = as_number(match[2])
|
|
130
|
+
return unless remote_as
|
|
131
|
+
|
|
132
|
+
state, prefixes = peer_state(match[3])
|
|
133
|
+
return unless state
|
|
134
|
+
|
|
135
|
+
scope.merge(peer: CtxParser::Values.host(match[1]), state: state, received_prefixes: prefixes, remote_as: remote_as)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# 仅在不存在平台附加标记时,末列数字才能证明会话已建立。
|
|
139
|
+
def peer_state(text)
|
|
140
|
+
raw_state = text.downcase
|
|
141
|
+
prefixes = Integer(raw_state, 10) if raw_state.match?(/\A\d+\z/)
|
|
142
|
+
[ prefixes ? :established : STATES[raw_state], prefixes ]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# 解析支持展示格式的 BGP 自治系统号。
|
|
146
|
+
def as_number(text)
|
|
147
|
+
parts = text.split(".").map { |value| Integer(value, 10) }
|
|
148
|
+
return if parts.size == 2 && parts.any? { |value| value > 65_535 }
|
|
149
|
+
|
|
150
|
+
value = parts.size == 2 ? parts[0] * 65_536 + parts[1] : parts.first
|
|
151
|
+
value if (1..4_294_967_295).cover?(value)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# 识别说明性回显,不将其作为数据记录。
|
|
155
|
+
def auxiliary?(line)
|
|
156
|
+
return true if /\A\d+\/\d+ BGP path\/bestpath attribute entries using \d+ bytes of memory\z/i.match?(line)
|
|
157
|
+
return true if /\A\d+ (?:BGP extended community entries using \d+ bytes of memory|received paths for inbound soft reconfiguration|networks peaked at .+)\z/i.match?(line)
|
|
158
|
+
|
|
159
|
+
line.match?(/\A(?:BGP router identifier |BGP table version |[\d ]+(?:network entries|path entries|BGP path attribute entries|BGP AS-PATH entries|BGP community entries|BGP route-map cache entries|BGP filter-list cache entries|paths using)|BGP using |BGP activity |BGP attribute entries |BGP community entries |BGP AS path entries |Neighbor sessions:|Memory used by BGP |BGP scan interval |Scan interval )/i)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
require "ctx_parser/values"
|
|
6
|
+
|
|
7
|
+
module CtxChecker
|
|
8
|
+
module CiscoIos
|
|
9
|
+
module Parsers
|
|
10
|
+
# OSPF 身份包含进程、VRF、接口和路由器 ID;未知作用域保留为 nil。
|
|
11
|
+
class ShowIpOspfNeighbor < CtxChecker::Parsers::Base
|
|
12
|
+
PLATFORM = :cisco_ios
|
|
13
|
+
STATES = { "FULL" => :full, "2WAY" => :two_way, "2-WAY" => :two_way, "DOWN" => :down,
|
|
14
|
+
"ATTEMPT" => :attempt, "INIT" => :init, "EXSTART" => :exstart, "EXCHANGE" => :exchange,
|
|
15
|
+
"LOADING" => :loading }.freeze
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# 解析 show ip ospf neighbor,保留不完整或不支持的证据。
|
|
20
|
+
def parse_output(text, complete:)
|
|
21
|
+
text = clean(text)
|
|
22
|
+
rows, issues = [], []
|
|
23
|
+
scope = { vrf: "default", process_id: nil, area: nil }
|
|
24
|
+
sections = []
|
|
25
|
+
section = { header: false, empty: false, count: 0, totals: [], declared: false, scope: scope }
|
|
26
|
+
sections << section
|
|
27
|
+
text.each_line.with_index(1) do |source, number|
|
|
28
|
+
line = source.strip
|
|
29
|
+
next if line.empty? || line.match?(/\A[-= ]+\z/)
|
|
30
|
+
|
|
31
|
+
if line.match?(/\ANeighbor ID\s+Pri\s+State\s+(?:Dead Time|Up Time)\s+Address\s+Interface\z/i)
|
|
32
|
+
section[:header] = true
|
|
33
|
+
elsif (match = /\AOSPF\s+Process\s+ID\s+(\S+)\s+VRF\s+(\S+)\z/i.match(line))
|
|
34
|
+
scope = { vrf: match[2], process_id: match[1], area: nil }
|
|
35
|
+
section = { header: false, empty: false, count: 0, totals: [], declared: true, scope: scope }
|
|
36
|
+
sections << section
|
|
37
|
+
elsif (match = /\A(?:Process ID|OSPF Process(?!\s+ID\b))\s*:?\s*(\S+)(?:,?\s+VRF\s+(\S+))?\z/i.match(line))
|
|
38
|
+
scope = { vrf: match[2] || "default", process_id: match[1], area: nil }
|
|
39
|
+
section = { header: false, empty: false, count: 0, totals: [], declared: true, scope: scope }
|
|
40
|
+
sections << section
|
|
41
|
+
elsif line.match?(/\A(?:OSPF\s+Process\b|Process ID\b)/i)
|
|
42
|
+
issues << { code: :invalid_protocol_scope, line: number }
|
|
43
|
+
scope = { vrf: nil, process_id: nil, area: nil }
|
|
44
|
+
section = { header: false, empty: false, count: 0, totals: [], declared: true, scope: scope }
|
|
45
|
+
sections << section
|
|
46
|
+
elsif (match = /\ATotal number of neighbors:\s*(\d+)\z/i.match(line))
|
|
47
|
+
section[:totals] << Integer(match[1], 10)
|
|
48
|
+
elsif line.match?(/\A(?:No (?:OSPF )?neighbors(?: found)?\.?|0 (?:OSPF )?neighbors)\z/i)
|
|
49
|
+
section[:empty] = true
|
|
50
|
+
elsif (row = parse_neighbor(line, scope))
|
|
51
|
+
rows << row
|
|
52
|
+
section[:count] += 1
|
|
53
|
+
else
|
|
54
|
+
issues << { code: :unparsed_row, line: number }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
sections.each do |entry|
|
|
58
|
+
entry[:totals].each do |total|
|
|
59
|
+
issues << { code: :count_mismatch, expected: total, observed: entry[:count] } unless total == entry[:count]
|
|
60
|
+
end
|
|
61
|
+
issues << { code: :contradictory_empty } if entry[:empty] && entry[:count].positive?
|
|
62
|
+
if entry[:count].positive? && !entry[:header]
|
|
63
|
+
issues << { code: :missing_neighbor_header }
|
|
64
|
+
elsif (entry[:header] || entry[:declared]) && entry[:count].zero? && !entry[:empty] && !entry[:totals].include?(0)
|
|
65
|
+
issues << { code: :neighbor_count_unavailable }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
recognized = sections.any? { |entry| entry[:empty] || entry[:totals].include?(0) || entry[:header] && entry[:count].positive? }
|
|
69
|
+
scopes = sections.filter_map do |entry|
|
|
70
|
+
value = entry[:scope].slice(:vrf, :process_id)
|
|
71
|
+
value if value.values.none?(&:nil?) && (entry[:empty] || entry[:totals].include?(0) || entry[:header] && entry[:count].positive?)
|
|
72
|
+
end
|
|
73
|
+
build_result(rows, complete: complete, recognized: recognized, issues: issues,
|
|
74
|
+
identity: %i[vrf process_id interface router_id], covered_scopes: scopes)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 解析协议邻居行的状态及稳定身份。
|
|
78
|
+
def parse_neighbor(line, scope)
|
|
79
|
+
match = /\A(\S+)\s+(\d+)\s+(FULL|2WAY|2-WAY|DOWN|ATTEMPT|INIT|EXSTART|EXCHANGE|LOADING)\/(?:\s*)(DR|BDR|DROTHER|-)\s+\S+\s+(\S+)\s+(\S+)\z/i.match(line)
|
|
80
|
+
return unless match && (0..255).cover?(Integer(match[2], 10)) &&
|
|
81
|
+
ipv4_address?(match[1]) && ipv4_address?(match[5])
|
|
82
|
+
|
|
83
|
+
scope.merge(interface: CtxChecker::Parsers::InterfaceNames.normalize(match[6], platform: self.class::PLATFORM),
|
|
84
|
+
router_id: CtxParser::Values.host(match[1]), neighbor_address: CtxParser::Values.host(match[5]),
|
|
85
|
+
state: STATES.fetch(match[3].upcase), network_type: :unknown, local_role: :unknown,
|
|
86
|
+
neighbor_role: match[4] == "-" ? :unknown : match[4].downcase.to_sym)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# OSPFv2 路由器 ID 与接口地址必须是 IPv4 主机地址。
|
|
90
|
+
def ipv4_address?(text)
|
|
91
|
+
text.match?(/\A\d+(?:\.\d+){3}\z/) && CtxParser::Values.valid_host?(text)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoIos
|
|
7
|
+
module Parsers
|
|
8
|
+
# IP 协议栈计数为累计值,与 ASIC 和接口计数区分处理。
|
|
9
|
+
class ShowIpTraffic < CtxChecker::Parsers::Base
|
|
10
|
+
METRICS = {
|
|
11
|
+
"format errors" => [ "format_errors", :error ], "checksum errors" => [ "checksum_errors", :error ],
|
|
12
|
+
"bad hop count" => [ "bad_ttl", :error ], "unknown protocol" => [ "unknown_protocol", :error ],
|
|
13
|
+
"not a gateway" => [ "not_a_gateway", :discard ], "security failures" => [ "security_failures", :policy ],
|
|
14
|
+
"bad options" => [ "bad_options", :error ], "encapsulation failed" => [ "encapsulation_failed", :discard ],
|
|
15
|
+
"unresolved" => [ "unresolved", :discard ], "no adjacency" => [ "no_adjacency", :discard ],
|
|
16
|
+
"no route" => [ "no_route", :discard ], "unicast RPF" => [ "unicast_rpf", :policy ],
|
|
17
|
+
"forced drop" => [ "forced_drop", :discard ], "options denied" => [ "options_denied", :policy ],
|
|
18
|
+
"source IP address zero" => [ "source_address_zero", :discard ],
|
|
19
|
+
"ignored" => [ "options_ignored", :policy ], "invalid hole" => [ "invalid_hole", :error ],
|
|
20
|
+
"unsupported-addr" => [ "unsupported_address", :discard ],
|
|
21
|
+
"timeouts" => [ "reassembly_timeouts", :error ], "couldn't reassemble" => [ "reassembly_failed", :error ],
|
|
22
|
+
"couldn't fragment" => [ "fragmentation_failed", :error ]
|
|
23
|
+
}.freeze
|
|
24
|
+
INFORMATIONAL = /\A\d+ (?:total|local destination|with options|end|nop|basic security|loose source route|timestamp|extended security|record route|stream ID|strict source route|alert|cipso|ump|other|reassembled|fragmented|fragments|received|sent|generated|forwarded)\z/i
|
|
25
|
+
REQUIRED_METRICS = %w[format_errors checksum_errors bad_ttl unknown_protocol encapsulation_failed no_route].freeze
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# 解析 show ip traffic,保留不完整或不支持的证据。
|
|
30
|
+
def parse_output(text, complete:)
|
|
31
|
+
records, issues = [], []
|
|
32
|
+
active = false
|
|
33
|
+
recognized = false
|
|
34
|
+
clean(text).each_line.with_index(1) do |source, number|
|
|
35
|
+
line = source.strip
|
|
36
|
+
next if line.empty?
|
|
37
|
+
if line == "IP statistics:"
|
|
38
|
+
active = recognized = true
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
if /\A\S+ statistics:/.match?(line)
|
|
42
|
+
active = false
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
next unless active
|
|
46
|
+
|
|
47
|
+
line.sub(/\A(?:Rcvd|Opts|Frags|Bcast|Mcast|Sent|Drop):\s*/, "").split(/,\s*/).each do |cell|
|
|
48
|
+
match = /\A(\d+) (.+)\z/.match(cell)
|
|
49
|
+
definition = match && METRICS[match[2]]
|
|
50
|
+
if definition
|
|
51
|
+
records << { component: "ipv4/control_plane", metric: definition[0], category: definition[1],
|
|
52
|
+
value: Integer(match[1], 10), discontinuity: nil }
|
|
53
|
+
elsif !INFORMATIONAL.match?(cell)
|
|
54
|
+
issues << { code: :unparsed_counter, line: number }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
missing = REQUIRED_METRICS - records.map { |record| record[:metric] }
|
|
59
|
+
issues << { code: :counter_fields_missing, count: missing.size } if recognized && missing.any?
|
|
60
|
+
build_result(records, complete: complete, recognized: recognized, issues: issues, identity: %i[component metric])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoIos
|
|
7
|
+
module Parsers
|
|
8
|
+
# 分别保留 IOS 各内存分配池,并使用精确字节数核算。
|
|
9
|
+
class ShowMemoryStatistics < CtxChecker::Parsers::Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# 解析 show memory statistics,保留不完整或不支持的证据。
|
|
13
|
+
def parse_output(text, complete:)
|
|
14
|
+
records, issues = [], []
|
|
15
|
+
clean(text).each_line.with_index(1) do |source, number|
|
|
16
|
+
line = source.strip
|
|
17
|
+
next if line.empty? || /\AHead\s+Total\(b\)/.match?(line) || /\ATracekey\s*:\s*[\da-f#]+\z/i.match?(line)
|
|
18
|
+
|
|
19
|
+
match = /\A(.+?)\s+Pool\s+Total:\s*(\d+)\s+Used:\s*(\d+)\s+Free:\s*(\d+)\z/i.match(line) ||
|
|
20
|
+
/\A(.+?)\s+[0-9a-f]+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+\s+\d+\z/i.match(line)
|
|
21
|
+
unless match
|
|
22
|
+
issues << { code: :unparsed_row, line: number }
|
|
23
|
+
next
|
|
24
|
+
end
|
|
25
|
+
total, used, free = match.captures.drop(1).map { |value| Integer(value, 10) }
|
|
26
|
+
consistent = total.positive? && used + free == total
|
|
27
|
+
issues << { code: :inconsistent_memory_totals, line: number } unless consistent
|
|
28
|
+
records << { component: match[1], scope: "ios_memory_pool", total_bytes: total, used_bytes: used,
|
|
29
|
+
free_bytes: free, available_bytes: nil, utilization: consistent ? used.fdiv(total) * 100 : nil, basis: :used_total }
|
|
30
|
+
end
|
|
31
|
+
build_result(records, complete: complete, recognized: records.any?, issues: issues, identity: %i[component scope])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "system_values"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module CiscoIos
|
|
8
|
+
module Parsers
|
|
9
|
+
# IOS 显式报告系统同步状态,并使用毫秒表示偏移。
|
|
10
|
+
class ShowNtpStatus < CtxChecker::Parsers::Base
|
|
11
|
+
include SystemValues
|
|
12
|
+
AUXILIARY = /\A(?:nominal freq|reference time|root dispersion|loopfilter state|system poll interval|ntp uptime|system time|leap time|leap direction|root delay|time resolution)/i
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 解析 show ntp status,保留不完整或不支持的证据。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
records, issues = [], []
|
|
19
|
+
clean(text).each_line.with_index(1) do |source, number|
|
|
20
|
+
line = source.strip
|
|
21
|
+
next if line.empty?
|
|
22
|
+
|
|
23
|
+
if (match = /\AClock is (synchronized|unsynchronized), stratum (\d+)(?:, reference assoc id \d+)?(?:, reference is (\S+))?\z/i.match(line))
|
|
24
|
+
stratum = Integer(match[2], 10)
|
|
25
|
+
issues << { code: :invalid_ntp_stratum, line: number } unless stratum.between?(0, 16)
|
|
26
|
+
records << { component: "system", synchronized: match[1].casecmp?("synchronized"),
|
|
27
|
+
stratum: stratum.between?(0, 16) ? stratum : nil, reference: match[3], peer: nil, offset_ms: nil, leap: nil }
|
|
28
|
+
elsif (match = /\Aclock offset is (#{NUMBER}) msec(?:,|\z)/i.match(line)) && records.one?
|
|
29
|
+
issues << { code: :duplicate_ntp_offset, line: number } if records.first[:offset_ms]
|
|
30
|
+
records.first[:offset_ms] = checked_number(match[1], issues: issues, component: "system")
|
|
31
|
+
elsif /\A%?NTP is not enabled\.?\z/i.match?(line)
|
|
32
|
+
records << { component: "system", synchronized: false, stratum: nil, reference: nil, peer: nil, offset_ms: nil, leap: nil }
|
|
33
|
+
elsif AUXILIARY.match?(line)
|
|
34
|
+
next
|
|
35
|
+
else
|
|
36
|
+
issues << unparsed(number)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
build_result(records, complete: complete, recognized: records.any?, issues: issues, identity: [ :component ])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|