ctx_checker 0.1.0 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +27 -0
- data/README-zh.md +10 -0
- data/README.md +10 -0
- data/docs/architecture-zh.md +2 -2
- data/lib/ctx_checker/checks/base.rb +4 -4
- data/lib/ctx_checker/checks/clock.rb +3 -3
- data/lib/ctx_checker/checks/counters.rb +7 -7
- data/lib/ctx_checker/checks/device_counters.rb +1 -1
- data/lib/ctx_checker/checks/interfaces.rb +1 -1
- data/lib/ctx_checker/checks/optics.rb +1 -1
- data/lib/ctx_checker/checks/temperature.rb +2 -0
- data/lib/ctx_checker/cisco_ios/parsers/show_clock_detail.rb +1 -1
- data/lib/ctx_checker/cisco_ios/parsers/show_environment_all.rb +3 -3
- data/lib/ctx_checker/cisco_ios/parsers/show_etherchannel_summary.rb +6 -3
- data/lib/ctx_checker/cisco_ios/parsers/show_facility_alarm_status.rb +1 -1
- data/lib/ctx_checker/cisco_ios/parsers/show_interfaces.rb +19 -5
- data/lib/ctx_checker/cisco_ios/parsers/show_interfaces_transceiver_detail.rb +3 -2
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_bgp_summary.rb +5 -5
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_ospf_neighbor.rb +3 -3
- data/lib/ctx_checker/cisco_ios/parsers/show_ip_traffic.rb +1 -1
- data/lib/ctx_checker/cisco_ios/parsers/show_memory_statistics.rb +1 -1
- data/lib/ctx_checker/cisco_ios/parsers/show_ntp_status.rb +1 -1
- data/lib/ctx_checker/cisco_ios/parsers/show_processes_cpu.rb +1 -1
- data/lib/ctx_checker/cisco_ios/profile.rb +3 -3
- data/lib/ctx_checker/cisco_nxos/parsers/show_environment.rb +3 -3
- data/lib/ctx_checker/cisco_nxos/parsers/show_interface_transceiver_details.rb +13 -8
- data/lib/ctx_checker/cisco_nxos/parsers/show_ip_traffic.rb +1 -1
- data/lib/ctx_checker/cisco_nxos/parsers/show_ntp_peer_status.rb +1 -1
- data/lib/ctx_checker/cisco_nxos/parsers/show_port_channel_summary.rb +2 -2
- data/lib/ctx_checker/cisco_nxos/parsers/show_system_resources.rb +8 -5
- data/lib/ctx_checker/cisco_nxos/profile.rb +3 -3
- data/lib/ctx_checker/h3c/parsers/display_bgp_peer.rb +2 -2
- data/lib/ctx_checker/h3c/parsers/display_cpu_usage.rb +3 -2
- data/lib/ctx_checker/h3c/parsers/display_interface.rb +2 -2
- data/lib/ctx_checker/h3c/parsers/display_link_aggregation_verbose.rb +1 -1
- data/lib/ctx_checker/h3c/parsers/display_ospf_peer.rb +2 -2
- data/lib/ctx_checker/h3c/parsers/display_transceiver_diagnosis.rb +1 -1
- data/lib/ctx_checker/h3c/profile.rb +3 -3
- data/lib/ctx_checker/hillstone/parsers/show_environment.rb +3 -3
- data/lib/ctx_checker/hillstone/parsers/show_interface.rb +2 -2
- data/lib/ctx_checker/hillstone/parsers/show_interface_counter.rb +1 -1
- data/lib/ctx_checker/hillstone/parsers/show_ntp_status.rb +2 -2
- data/lib/ctx_checker/hillstone/parsers/show_transceiver.rb +1 -1
- data/lib/ctx_checker/hillstone/profile.rb +3 -3
- data/lib/ctx_checker/observation.rb +13 -8
- data/lib/ctx_checker/palo_alto/parsers/show_clock.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_counter_global.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_counter_interface_all.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_interface_all.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_lacp_aggregate_ethernet.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_ntp.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_routing_protocol_bgp_peer.rb +3 -3
- data/lib/ctx_checker/palo_alto/parsers/show_routing_protocol_ospf_neighbor.rb +4 -4
- data/lib/ctx_checker/palo_alto/parsers/show_system_environmentals.rb +3 -3
- data/lib/ctx_checker/palo_alto/parsers/show_system_resources.rb +3 -3
- data/lib/ctx_checker/palo_alto/parsers/show_system_state_alarms.rb +1 -1
- data/lib/ctx_checker/palo_alto/parsers/show_transceiver_all.rb +1 -1
- data/lib/ctx_checker/palo_alto/profile.rb +3 -3
- data/lib/ctx_checker/parsers/base.rb +11 -4
- data/lib/ctx_checker/parsers/diagnostics.rb +47 -0
- data/lib/ctx_checker/policy.rb +20 -12
- data/lib/ctx_checker/radware_alteon/parsers/info_l2_lacp_dump.rb +3 -3
- data/lib/ctx_checker/radware_alteon/parsers/info_l2_trunk.rb +4 -4
- data/lib/ctx_checker/radware_alteon/parsers/info_l3_ip.rb +1 -1
- data/lib/ctx_checker/radware_alteon/parsers/info_link.rb +1 -1
- data/lib/ctx_checker/radware_alteon/parsers/info_transceiver.rb +1 -1
- data/lib/ctx_checker/radware_alteon/parsers/stats_port_ether.rb +1 -1
- data/lib/ctx_checker/radware_alteon/parsers/stats_port_if.rb +1 -1
- data/lib/ctx_checker/radware_alteon/parsers/stats_sp_allutils.rb +3 -3
- data/lib/ctx_checker/radware_alteon/parsers/stats_sys_ntp.rb +3 -3
- data/lib/ctx_checker/radware_alteon/profile.rb +9 -9
- data/lib/ctx_checker/runtime_parser.rb +16 -10
- data/lib/ctx_checker/{system_record.rb → system_fields.rb} +3 -3
- data/lib/ctx_checker/system_policy.rb +14 -14
- data/lib/ctx_checker/version.rb +1 -1
- data/lib/ctx_checker.rb +1 -1
- metadata +11 -10
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
# 保留 DOM 不可用和模块缺失状态,不虚构光功率。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
text = clean(text)
|
|
19
|
-
rows, issues, devices = [],
|
|
19
|
+
rows, issues, devices = [], diagnostics, []
|
|
20
20
|
current, lane, header, lane_valid = nil, nil, false, true
|
|
21
21
|
text.each_line.with_index(1) do |source, number|
|
|
22
22
|
line = source.strip
|
|
@@ -62,7 +62,8 @@ module CtxChecker
|
|
|
62
62
|
issues << { code: :unparsed_row, line: number }
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
-
|
|
65
|
+
by_interface = rows.group_by { |row| row[:interface] }
|
|
66
|
+
devices.each { |device| complete_measurements(rows, device, issues, by_interface) }
|
|
66
67
|
build_result(rows, complete: complete, recognized: devices.any?, issues: issues, identity: %i[interface lane metric])
|
|
67
68
|
end
|
|
68
69
|
|
|
@@ -80,8 +81,9 @@ module CtxChecker
|
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
# 即使配对指标解析成功,缺失指标仍须明确保留。
|
|
83
|
-
def complete_measurements(rows, device, issues)
|
|
84
|
-
device_rows =
|
|
84
|
+
def complete_measurements(rows, device, issues, by_interface)
|
|
85
|
+
device_rows = by_interface[device[:interface]] ||= []
|
|
86
|
+
measurements = device_rows.to_h { |row| [ row.values_at(:lane, :metric), true ] }
|
|
85
87
|
if device[:applicable] == false && (!device[:presence_states].empty? || device[:lanes].any? || device_rows.any?)
|
|
86
88
|
issues << { code: :contradictory_optical_applicability, interface: device[:interface] }
|
|
87
89
|
device[:applicable] = nil
|
|
@@ -90,15 +92,18 @@ module CtxChecker
|
|
|
90
92
|
if device[:presence_states].uniq.size > 1
|
|
91
93
|
issues << { code: :contradictory_transceiver_presence, interface: device[:interface] }
|
|
92
94
|
end
|
|
93
|
-
if device[:dom_unsupported] &&
|
|
95
|
+
if device[:dom_unsupported] && device_rows.any? { |row| !row[:value].nil? }
|
|
94
96
|
issues << { code: :contradictory_dom_support, interface: device[:interface] }
|
|
95
97
|
end
|
|
96
98
|
lanes = device[:lanes].empty? ? [ nil ] : device[:lanes].uniq
|
|
97
99
|
lanes.each do |lane|
|
|
98
100
|
%i[tx_power rx_power].each do |metric|
|
|
99
|
-
next if
|
|
101
|
+
next if measurements.key?([ lane, metric ])
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
record = base_record(device, lane, metric)
|
|
104
|
+
rows << record
|
|
105
|
+
device_rows << record
|
|
106
|
+
measurements[[ lane, metric ]] = true
|
|
102
107
|
unless device[:present] == false || device[:dom_supported] == false
|
|
103
108
|
issues << { code: :optical_measurement_missing, interface: device[:interface], lane: lane, metric: metric }
|
|
104
109
|
end
|
|
@@ -107,7 +112,7 @@ module CtxChecker
|
|
|
107
112
|
if device[:present].nil? && device[:applicable] != false
|
|
108
113
|
issues << { code: :transceiver_presence_unknown, interface: device[:interface] }
|
|
109
114
|
end
|
|
110
|
-
if device[:present] == false &&
|
|
115
|
+
if device[:present] == false && device_rows.any? { |row| !row[:value].nil? }
|
|
111
116
|
issues << { code: :contradictory_transceiver_presence, interface: device[:interface] }
|
|
112
117
|
end
|
|
113
118
|
end
|
|
@@ -12,7 +12,7 @@ module CtxChecker
|
|
|
12
12
|
|
|
13
13
|
# 解析 show ntp peer status,保留不完整或不支持的证据。
|
|
14
14
|
def parse_output(text, complete:)
|
|
15
|
-
peers, issues, totals = [],
|
|
15
|
+
peers, issues, totals = [], diagnostics, []
|
|
16
16
|
vrf_column = nil
|
|
17
17
|
each_ntp_line(clean(text), issues) do |line, number|
|
|
18
18
|
next if line.empty? || /\A-+\z/.match?(line)
|
|
@@ -12,8 +12,8 @@ module CtxChecker
|
|
|
12
12
|
private
|
|
13
13
|
|
|
14
14
|
# 部分终端会将下一表格行附加在占满终端宽度的分隔线后。
|
|
15
|
-
def
|
|
16
|
-
super
|
|
15
|
+
def clean(text)
|
|
16
|
+
super.gsub(/^([ \t]*-{3,})(?=Group\s+Port-|\d+\s+(?:Po|port-channel)\d+\()/i, "\\1\n")
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
4
|
require_relative "../../cisco_ios/parsers/system_values"
|
|
5
|
-
require "ctx_parser/
|
|
5
|
+
require "ctx_parser/results"
|
|
6
6
|
|
|
7
7
|
module CtxChecker
|
|
8
8
|
module CiscoNxos
|
|
@@ -15,9 +15,10 @@ module CtxChecker
|
|
|
15
15
|
|
|
16
16
|
# 解析 show system resources,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
|
-
cpu, memory, cpu_issues, memory_issues = [], [],
|
|
18
|
+
cpu, memory, cpu_issues, memory_issues = [], [], diagnostics, diagnostics
|
|
19
19
|
caches, statuses = [], []
|
|
20
20
|
kernel_cache = {}
|
|
21
|
+
duplicate_cache = false
|
|
21
22
|
clean(text).each_line.with_index(1) do |source, number|
|
|
22
23
|
line = source.strip
|
|
23
24
|
next if line.empty?
|
|
@@ -38,7 +39,10 @@ module CtxChecker
|
|
|
38
39
|
caches << match.captures.sum { |value| Integer(value, 10) * 1024 }
|
|
39
40
|
elsif (match = /\AKernel (buffers|cached)\s*:\s*(\d+)K Used\z/i.match(line))
|
|
40
41
|
key = match[1].downcase
|
|
41
|
-
|
|
42
|
+
if kernel_cache.key?(key)
|
|
43
|
+
duplicate_cache = true
|
|
44
|
+
memory_issues << { code: :duplicate_memory_cache, line: number }
|
|
45
|
+
end
|
|
42
46
|
kernel_cache[key] = Integer(match[2], 10) * 1024
|
|
43
47
|
elsif (match = /\ACurrent memory status:\s*(.+)\z/i.match(line))
|
|
44
48
|
state = match[1].casecmp?("severe") ? :critical : component_status(match[1])
|
|
@@ -56,7 +60,6 @@ module CtxChecker
|
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
memory_issues << { code: :incomplete_memory_cache } if kernel_cache.size == 1
|
|
59
|
-
duplicate_cache = memory_issues.any? { |issue| issue[:code] == :duplicate_memory_cache }
|
|
60
63
|
caches << kernel_cache.values.sum if kernel_cache.size == 2 && !duplicate_cache
|
|
61
64
|
memory_issues << { code: :duplicate_memory_cache } if caches.size > 1
|
|
62
65
|
memory_issues << { code: :duplicate_memory_status } if statuses.size > 1
|
|
@@ -78,7 +81,7 @@ module CtxChecker
|
|
|
78
81
|
memory_issues << { code: :inconsistent_memory_cache }
|
|
79
82
|
end
|
|
80
83
|
end
|
|
81
|
-
CtxParser::
|
|
84
|
+
CtxParser::Results.new(by_dimension: {
|
|
82
85
|
cpu: build_result(cpu, complete: complete, recognized: cpu.any?, issues: cpu_issues, identity: %i[component scope window_seconds]),
|
|
83
86
|
memory: build_result(memory, complete: complete, recognized: memory.any?, issues: memory_issues, identity: %i[component scope])
|
|
84
87
|
})
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
module CiscoNxos
|
|
17
17
|
# NX-OS 命令选择保留完整接口详情,并明确 IPv4 BGP 作用域。
|
|
18
18
|
class Profile < CtxParser::Profile
|
|
19
|
-
|
|
19
|
+
DECLARATIONS = CtxParser::Commands.new([
|
|
20
20
|
{ dimensions: %i[interfaces counters], command: "show interface", parser: Parsers::ShowInterface },
|
|
21
21
|
{ dimensions: [ :aggregation ], command: "show port-channel summary", parser: Parsers::ShowPortChannelSummary },
|
|
22
22
|
{ dimensions: [ :optics ], command: "show interface transceiver details", parser: Parsers::ShowInterfaceTransceiverDetails },
|
|
@@ -28,8 +28,8 @@ module CtxChecker
|
|
|
28
28
|
{ dimensions: [ :ntp ], command: "show ntp peer-status", parser: Parsers::ShowNtpPeerStatus },
|
|
29
29
|
{ dimensions: [ :device_counters ], command: "show ip traffic", parser: Parsers::ShowIpTraffic }
|
|
30
30
|
])
|
|
31
|
-
COMMANDS =
|
|
32
|
-
PARSERS =
|
|
31
|
+
COMMANDS = DECLARATIONS.by_dimension
|
|
32
|
+
PARSERS = DECLARATIONS.parsers
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -14,7 +14,7 @@ module CtxChecker
|
|
|
14
14
|
# 解析 display bgp peer,保留不完整或不支持的证据。
|
|
15
15
|
def parse_output(text, complete:)
|
|
16
16
|
text = clean(text)
|
|
17
|
-
rows, issues, total, header = [],
|
|
17
|
+
rows, issues, total, header = [], diagnostics, nil, false
|
|
18
18
|
text.each_line do |raw|
|
|
19
19
|
line = raw.strip
|
|
20
20
|
if (match = /\ATotal number of peers:\s*(\d+)\z/i.match(line))
|
|
@@ -24,7 +24,7 @@ module CtxChecker
|
|
|
24
24
|
header = true
|
|
25
25
|
elsif (match = /\A\*?\s*([\d.]+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\S+\s+(.+)\z/.match(line))
|
|
26
26
|
state = match[4].downcase.delete(" ")
|
|
27
|
-
if CtxParser::Values.
|
|
27
|
+
if CtxParser::Values.ip_address?(match[1]) && STATES.include?(state) && (1..4_294_967_295).cover?(match[2].to_i)
|
|
28
28
|
rows << { vrf: "default", afi_safi: "ipv4_unicast", peer: match[1], state: state == "idle(admin)" ? :shutdown : state.to_sym,
|
|
29
29
|
received_prefixes: match[3].to_i, remote_as: match[2].to_i }
|
|
30
30
|
else
|
|
@@ -45,8 +45,9 @@ module CtxChecker
|
|
|
45
45
|
@issues << { code: :unrecognized_cpu_line }
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
windows = rows.group_by { |row| row[:component] }.transform_values { |samples| samples.map { |row| row[:window_seconds] }.uniq }
|
|
49
|
+
components.uniq.each do |component|
|
|
50
|
+
missing = [ 5, 60, 300 ] - windows.fetch(component, [])
|
|
50
51
|
@issues << { code: :missing_cpu_windows, component: component, count: missing.size } if missing.any?
|
|
51
52
|
end
|
|
52
53
|
build_result(rows, complete: complete, recognized: components.any?, identity: %i[component scope window_seconds])
|
|
@@ -24,7 +24,7 @@ module CtxChecker
|
|
|
24
24
|
# 接口状态独立于各配置块提供的计数器进行投影。
|
|
25
25
|
def parse_output(text, complete:)
|
|
26
26
|
text = clean(text)
|
|
27
|
-
interfaces, counters, interface_issues, counter_issues = [], [],
|
|
27
|
+
interfaces, counters, interface_issues, counter_issues = [], [], diagnostics, diagnostics
|
|
28
28
|
sections = text.split(/(?=^[ \t]*#{INTERFACE}(?:[ \t]+current state[ \t]*:.*)?[ \t]*$)/i)
|
|
29
29
|
sections.each do |section|
|
|
30
30
|
header = /\A[ \t]*(#{INTERFACE})(?:[ \t]+current state[ \t]*:[ \t]*([^\n]+))?[ \t]*\n/i.match(section)
|
|
@@ -73,7 +73,7 @@ module CtxChecker
|
|
|
73
73
|
end
|
|
74
74
|
counters.concat(section_counters)
|
|
75
75
|
end
|
|
76
|
-
CtxParser::
|
|
76
|
+
CtxParser::Results.new(by_dimension: {
|
|
77
77
|
interfaces: build_result(interfaces, complete: complete, recognized: interfaces.any?, issues: interface_issues, identity: [ :name ]),
|
|
78
78
|
counters: build_result(counters, complete: complete, recognized: counters.any?, issues: counter_issues + interface_issues, identity: %i[interface metric])
|
|
79
79
|
})
|
|
@@ -21,7 +21,7 @@ module CtxChecker
|
|
|
21
21
|
if /\AInfo:\s+No link aggregation group defined at present\.\z/i.match?(text.strip)
|
|
22
22
|
return build_result([], complete: complete, recognized: true, issues: [ { code: :aggregation_not_configured } ])
|
|
23
23
|
end
|
|
24
|
-
rows, issues, group, remote, groups, columns, tables = [],
|
|
24
|
+
rows, issues, group, remote, groups, columns, tables = [], diagnostics, nil, false, {}, nil, []
|
|
25
25
|
text.each_line do |raw|
|
|
26
26
|
line = raw.strip
|
|
27
27
|
if (match = /\AAggregate Interface:\s*(.*)\z/i.match(line))
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
if /\AOSPF is not configured\.\z/i.match?(text.strip)
|
|
17
17
|
return build_result([], complete: complete, recognized: true, issues: [ { code: :protocol_not_configured } ])
|
|
18
18
|
end
|
|
19
|
-
rows, issues, process, area, header = [],
|
|
19
|
+
rows, issues, process, area, header = [], diagnostics, nil, nil, false
|
|
20
20
|
text.each_line do |raw|
|
|
21
21
|
line = raw.strip
|
|
22
22
|
if (match = /\AOSPF Process (\d+) with Router ID ([\d.]+)\z/i.match(line))
|
|
@@ -26,7 +26,7 @@ module CtxChecker
|
|
|
26
26
|
elsif /\ARouter ID\s+Address\s+Pri\s+Dead-Time\s+State\s+Interface\z/i.match?(line)
|
|
27
27
|
header = true
|
|
28
28
|
elsif (match = /\A([\d.]+)\s+([\d.]+)\s+\d+\s+\S+\s+(Down|Init|Attempt|2-Way|Exstart|Exchange|Loading|Full)(?:\/[ \t]*(DR|BDR|DROther|-))?\s+(\S+)\z/i.match(line))
|
|
29
|
-
if CtxParser::Values.
|
|
29
|
+
if CtxParser::Values.ip_address?(match[1]) && CtxParser::Values.ip_address?(match[2])
|
|
30
30
|
issues << { code: :ospf_scope_missing } unless process && area
|
|
31
31
|
rows << { vrf: nil, process_id: process, area: area,
|
|
32
32
|
interface: CtxChecker::Parsers::InterfaceNames.normalize(match[5], platform: :h3c), router_id: match[1], neighbor_address: match[2],
|
|
@@ -18,7 +18,7 @@ module CtxChecker
|
|
|
18
18
|
# 解析 display transceiver diagnosis,保留不完整或不支持的证据。
|
|
19
19
|
def parse_output(text, complete:)
|
|
20
20
|
text = clean(text)
|
|
21
|
-
rows, issues = [],
|
|
21
|
+
rows, issues = [], diagnostics
|
|
22
22
|
text.split(/(?=^\S+ transceiver diagnostic information:)/i).each do |block|
|
|
23
23
|
next if block.strip.empty?
|
|
24
24
|
match = /\A(\S+) transceiver diagnostic information:/i.match(block)
|
|
@@ -22,7 +22,7 @@ module CtxChecker
|
|
|
22
22
|
module H3c
|
|
23
23
|
# 声明 Comware 只读查询;BGP 明确限定为公网 IPv4 单播。
|
|
24
24
|
class Profile < CtxParser::Profile
|
|
25
|
-
|
|
25
|
+
DECLARATIONS = CtxParser::Commands.new([
|
|
26
26
|
{ dimensions: %i[interfaces counters], command: "display interface", parser: Parsers::DisplayInterface },
|
|
27
27
|
{ dimensions: [ :counters ], command: "display counters inbound interface", parser: Parsers::DisplayCountersInboundInterface },
|
|
28
28
|
{ dimensions: [ :counters ], command: "display counters outbound interface", parser: Parsers::DisplayCountersOutboundInterface },
|
|
@@ -39,8 +39,8 @@ module CtxChecker
|
|
|
39
39
|
{ dimensions: [ :ntp ], command: "display ntp-service status", parser: Parsers::DisplayNtpServiceStatus },
|
|
40
40
|
{ dimensions: [ :alarms ], command: "display alarm active", parser: Parsers::DisplayAlarmActive }
|
|
41
41
|
], multi_command: true)
|
|
42
|
-
COMMANDS =
|
|
43
|
-
PARSERS =
|
|
42
|
+
COMMANDS = DECLARATIONS.by_dimension
|
|
43
|
+
PARSERS = DECLARATIONS.parsers
|
|
44
44
|
|
|
45
45
|
# 每个已观察接口都必须保留入站与出站两个方向的证据状态。
|
|
46
46
|
def counter_metrics = %i[in_errors out_errors].freeze
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
|
-
require "ctx_parser/
|
|
4
|
+
require "ctx_parser/results"
|
|
5
5
|
|
|
6
6
|
module CtxChecker
|
|
7
7
|
module Hillstone
|
|
@@ -20,7 +20,7 @@ module CtxChecker
|
|
|
20
20
|
# 跟踪各已声明章节,并分别要求其具备表头与测量值。
|
|
21
21
|
def parse_output(text, complete:)
|
|
22
22
|
rows = { temperature: [], environment: [], power: [] }
|
|
23
|
-
issues = rows.transform_values {
|
|
23
|
+
issues = rows.transform_values { diagnostics }
|
|
24
24
|
contexts, section, header = [], nil, false
|
|
25
25
|
clean(text).each_line.with_index(1) do |raw, number|
|
|
26
26
|
line = raw.strip
|
|
@@ -52,7 +52,7 @@ module CtxChecker
|
|
|
52
52
|
issues[context[:section]] << { code: :incomplete_environment_section }
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
CtxParser::
|
|
55
|
+
CtxParser::Results.new(by_dimension: rows.to_h do |dimension, records|
|
|
56
56
|
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
57
57
|
[ dimension, build_result(records, complete: complete, recognized: records.any?, issues: issues[dimension], identity: identity) ]
|
|
58
58
|
end)
|
|
@@ -17,7 +17,7 @@ module CtxChecker
|
|
|
17
17
|
|
|
18
18
|
# 识别无地址链路时,仍保留 IP 协议 down 状态。
|
|
19
19
|
def parse_output(text, complete:)
|
|
20
|
-
rows, issues, header = [],
|
|
20
|
+
rows, issues, header = [], diagnostics, false
|
|
21
21
|
clean(text).each_line do |raw|
|
|
22
22
|
line = raw.strip
|
|
23
23
|
next if line.empty? || /\A[-=]+\z/.match?(line) || LEGEND.match?(line)
|
|
@@ -38,7 +38,7 @@ module CtxChecker
|
|
|
38
38
|
{ group: row[:name], member: nil, member_status: :unknown, oper_status: row[:oper_status] }
|
|
39
39
|
end
|
|
40
40
|
missing_members = groups.map { |group| { code: :aggregation_members_unavailable, group: group[:group] } }
|
|
41
|
-
CtxParser::
|
|
41
|
+
CtxParser::Results.new(by_dimension: {
|
|
42
42
|
interfaces: interfaces,
|
|
43
43
|
aggregation: build_result(groups, complete: complete, recognized: header,
|
|
44
44
|
issues: interfaces.issues + missing_members, identity: %i[group member])
|
|
@@ -14,7 +14,7 @@ module CtxChecker
|
|
|
14
14
|
|
|
15
15
|
# 解释计数器前先校验列顺序;缺少采样时保留 nil。
|
|
16
16
|
def parse_output(text, complete:)
|
|
17
|
-
rows, issues, header, count = [],
|
|
17
|
+
rows, issues, header, count = [], diagnostics, false, 0
|
|
18
18
|
clean(text).each_line do |raw|
|
|
19
19
|
line = raw.strip
|
|
20
20
|
next if line.empty? || /\A[-=]+\z/.match?(line)
|
|
@@ -59,7 +59,7 @@ module CtxChecker
|
|
|
59
59
|
fields[key] = value
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
-
peer = CtxParser::Values.
|
|
62
|
+
peer = CtxParser::Values.ip_address?(host) ? CtxParser::Values.ip_address(host) : host.downcase
|
|
63
63
|
{ component: "peer/#{fields[:vrouter] || 'unspecified'}/#{peer}", peer: peer, synchronized: nil,
|
|
64
64
|
last_sync_status: fields[:last_sync_status] || :unknown, last_sync_at: nil }
|
|
65
65
|
end
|
|
@@ -99,7 +99,7 @@ module CtxChecker
|
|
|
99
99
|
|
|
100
100
|
# 服务器名称仅接受主机名或主机地址,不接受任意 CLI 片段。
|
|
101
101
|
def valid_peer?(text)
|
|
102
|
-
return true if CtxParser::Values.
|
|
102
|
+
return true if CtxParser::Values.ip_address?(text)
|
|
103
103
|
return false if text.match?(/\A[\d.]+\z/) || text.length > 253
|
|
104
104
|
|
|
105
105
|
text.delete_suffix(".").split(".", -1).all? { |label| /\A[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\z/.match?(label) }
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
|
|
17
17
|
# 逐模块隔离,避免未知块将测量值错误关联到前一模块。
|
|
18
18
|
def parse_output(text, complete:)
|
|
19
|
-
rows, issues, blocks, current, header = [],
|
|
19
|
+
rows, issues, blocks, current, header = [], diagnostics, [], nil, false
|
|
20
20
|
clean(text).each_line do |raw|
|
|
21
21
|
line = raw.strip
|
|
22
22
|
next if line.empty? || /\A=+\z/.match?(line)
|
|
@@ -17,7 +17,7 @@ module CtxChecker
|
|
|
17
17
|
module Hillstone
|
|
18
18
|
# StoneOS 命令已在 SG-6000-A3700 验证;未覆盖作用域明确保留不可用。
|
|
19
19
|
class Profile < CtxParser::Profile
|
|
20
|
-
|
|
20
|
+
DECLARATIONS = CtxParser::Commands.new([
|
|
21
21
|
{ dimensions: %i[interfaces aggregation], command: "show interface", parser: Parsers::ShowInterface },
|
|
22
22
|
{ dimensions: [ :counters ], command: "show interface counter", parser: Parsers::ShowInterfaceCounter },
|
|
23
23
|
{ dimensions: [ :optics ], command: "show transceiver", parser: Parsers::ShowTransceiver },
|
|
@@ -30,8 +30,8 @@ module CtxChecker
|
|
|
30
30
|
{ dimensions: [ :ntp ], command: "show ntp status", parser: Parsers::ShowNtpStatus },
|
|
31
31
|
{ dimensions: [ :device_counters ], command: "show cpu-cntr", parser: Parsers::ShowCpuCntr }
|
|
32
32
|
])
|
|
33
|
-
COMMANDS =
|
|
34
|
-
PARSERS =
|
|
33
|
+
COMMANDS = DECLARATIONS.by_dimension
|
|
34
|
+
PARSERS = DECLARATIONS.parsers
|
|
35
35
|
|
|
36
36
|
# 在线接口丢弃表不提供错误包或 CRC 测量。
|
|
37
37
|
def counter_metrics = %i[in_errors out_errors crc_errors].freeze
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require "ctx_parser/immutable"
|
|
4
4
|
require "time"
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "system_fields"
|
|
6
6
|
|
|
7
7
|
module CtxChecker
|
|
8
8
|
# 提供经过校验的不可变运行事实,与后续健康评估相互独立。
|
|
9
9
|
class Observation
|
|
10
|
-
NAMES = (%i[interfaces counters aggregation optics bgp ospf] +
|
|
10
|
+
NAMES = (%i[interfaces counters aggregation optics bgp ospf] + SystemFields::FIELDS.keys).freeze
|
|
11
11
|
STATUSES = %i[complete partial failed unsupported not_requested not_applicable].freeze
|
|
12
12
|
METRICS = %i[in_errors out_errors in_discards out_discards crc_errors stomped_crc_errors out_carrier_errors].freeze
|
|
13
13
|
COUNTER_UNITS = %i[packets bytes events unknown].freeze
|
|
@@ -19,13 +19,13 @@ module CtxChecker
|
|
|
19
19
|
aggregation: %i[group member member_status oper_status],
|
|
20
20
|
optics: %i[interface lane metric value unit low_alarm low_warning high_warning high_alarm alarm present dom_supported applicable],
|
|
21
21
|
bgp: %i[vrf afi_safi peer state received_prefixes remote_as],
|
|
22
|
-
ospf: %i[vrf process_id area interface router_id neighbor_address state network_type local_role neighbor_role], **
|
|
22
|
+
ospf: %i[vrf process_id area interface router_id neighbor_address state network_type local_role neighbor_role], **SystemFields::FIELDS
|
|
23
23
|
}.transform_values { |fields| fields + [ :source_complete ] })
|
|
24
24
|
IDENTITIES = CtxParser::Immutable.copy(interfaces: %i[name], counters: %i[interface metric],
|
|
25
25
|
aggregation: %i[group member], optics: %i[interface lane metric], bgp: %i[vrf afi_safi peer],
|
|
26
|
-
ospf: %i[vrf process_id interface router_id neighbor_address], **
|
|
26
|
+
ospf: %i[vrf process_id interface router_id neighbor_address], **SystemFields::IDENTITIES)
|
|
27
27
|
REQUIRED = CtxParser::Immutable.copy(interfaces: %i[name], counters: %i[interface metric],
|
|
28
|
-
aggregation: %i[group], optics: %i[interface metric], bgp: %i[peer], ospf: %i[router_id], **
|
|
28
|
+
aggregation: %i[group], optics: %i[interface metric], bgp: %i[peer], ospf: %i[router_id], **SystemFields::REQUIRED)
|
|
29
29
|
ISSUE_FIELDS = %i[code line lines field count skipped interface metric metrics lane section expected observed group stage component scope window_seconds command error_type].freeze
|
|
30
30
|
ERROR_TYPE_PATTERN = /\A(?:[A-Z][A-Za-z0-9_]*::)*[A-Z][A-Za-z0-9_]*\z/
|
|
31
31
|
ENUMS = CtxParser::Immutable.copy(
|
|
@@ -48,6 +48,10 @@ module CtxChecker
|
|
|
48
48
|
end
|
|
49
49
|
@name, @status = name, status
|
|
50
50
|
validate_scopes!(covered_scopes)
|
|
51
|
+
covered_scopes = covered_scopes.map do |scope|
|
|
52
|
+
scope[:process_id].is_a?(Integer) ? scope.merge(process_id: scope[:process_id].to_s) : scope
|
|
53
|
+
end
|
|
54
|
+
validate_scopes!(covered_scopes)
|
|
51
55
|
normalized = records.map { |record| validate_record(record) }
|
|
52
56
|
identities = normalized.map { |record| identity(record) }
|
|
53
57
|
raise ArgumentError, "Duplicate observation identity" unless identities.uniq.size == identities.size
|
|
@@ -128,10 +132,10 @@ module CtxChecker
|
|
|
128
132
|
next if value.nil?
|
|
129
133
|
valid = if key == :source_complete
|
|
130
134
|
value == true || value == false
|
|
131
|
-
elsif
|
|
132
|
-
|
|
135
|
+
elsif SystemFields::FIELDS.key?(name)
|
|
136
|
+
SystemFields.valid_value?(name, key, value)
|
|
133
137
|
elsif key == :sampled_at
|
|
134
|
-
|
|
138
|
+
SystemFields.timestamp?(value)
|
|
135
139
|
elsif name == :counters && key == :unit
|
|
136
140
|
COUNTER_UNITS.include?(value)
|
|
137
141
|
elsif name == :counters && key == :scope
|
|
@@ -156,6 +160,7 @@ module CtxChecker
|
|
|
156
160
|
end
|
|
157
161
|
raise ArgumentError, "Invalid #{name} field #{key}" unless valid
|
|
158
162
|
end
|
|
163
|
+
row[:process_id] = row[:process_id].to_s if name == :ospf && row[:process_id].is_a?(Integer)
|
|
159
164
|
row
|
|
160
165
|
end
|
|
161
166
|
|
|
@@ -14,7 +14,7 @@ module CtxChecker
|
|
|
14
14
|
# 解析 show clock,保留不完整或不支持的证据。
|
|
15
15
|
def parse_output(text, complete:)
|
|
16
16
|
source = clean(text)
|
|
17
|
-
rows, issues = [],
|
|
17
|
+
rows, issues = [], diagnostics
|
|
18
18
|
source.each_line.with_index(1) do |raw, number|
|
|
19
19
|
next if raw.strip.empty?
|
|
20
20
|
match = /\A(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+([A-Z][a-z]{2})\s+(\d{1,2})\s+(\d{2}:\d{2}:\d{2})\s+(\S+)\s+(\d{4})\z/.match(raw.strip)
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
# 解析 show counter global,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
source = clean(text)
|
|
19
|
-
rows, totals, issues = [], [],
|
|
19
|
+
rows, totals, issues = [], [], diagnostics
|
|
20
20
|
title, header = false, false
|
|
21
21
|
source.each_line.with_index(1) do |raw, number|
|
|
22
22
|
line = raw.strip
|
|
@@ -19,7 +19,7 @@ module CtxChecker
|
|
|
19
19
|
# 解析 show counter interface all,保留不完整或不支持的证据。
|
|
20
20
|
def parse_output(text, complete:)
|
|
21
21
|
text = clean(text)
|
|
22
|
-
rows, issues, interfaces = [],
|
|
22
|
+
rows, issues, interfaces = [], diagnostics, {}
|
|
23
23
|
text.split(/(?=^\s*Interface:)/i).each do |block|
|
|
24
24
|
next if block.strip.empty?
|
|
25
25
|
match = /\A\s*Interface:\s*([^\n]+)\n/i.match(block)
|
|
@@ -13,7 +13,7 @@ module CtxChecker
|
|
|
13
13
|
# 解析 show interface all,保留不完整或不支持的证据。
|
|
14
14
|
def parse_output(text, complete:)
|
|
15
15
|
text = clean(text)
|
|
16
|
-
rows, issues, section, totals, counts, duplicates = {},
|
|
16
|
+
rows, issues, section, totals, counts, duplicates = {}, diagnostics, nil, {}, Hash.new(0), []
|
|
17
17
|
logical_names = {}
|
|
18
18
|
text.each_line do |raw|
|
|
19
19
|
line = raw.strip
|
|
@@ -13,7 +13,7 @@ module CtxChecker
|
|
|
13
13
|
# 解析 show lacp aggregate ethernet,保留不完整或不支持的证据。
|
|
14
14
|
def parse_output(text, complete:)
|
|
15
15
|
text = clean(text)
|
|
16
|
-
rows, issues, group, header, groups = [],
|
|
16
|
+
rows, issues, group, header, groups = [], diagnostics, nil, false, {}
|
|
17
17
|
text.each_line do |raw|
|
|
18
18
|
line = raw.strip
|
|
19
19
|
if (match = /\AAE group:\s*(ae\d+)\z/i.match(line))
|
|
@@ -12,7 +12,7 @@ module CtxChecker
|
|
|
12
12
|
# 解析 show ntp,保留不完整或不支持的证据。
|
|
13
13
|
def parse_output(text, complete:)
|
|
14
14
|
source = clean(text)
|
|
15
|
-
rows, globals, issues = [], [],
|
|
15
|
+
rows, globals, issues = [], [], diagnostics
|
|
16
16
|
current, fields, header, peer_fields = nil, {}, false, {}
|
|
17
17
|
source.each_line.with_index(1) do |raw, number|
|
|
18
18
|
line = raw.strip
|
|
@@ -15,7 +15,7 @@ module CtxChecker
|
|
|
15
15
|
# 解析 show routing protocol bgp peer,保留不完整或不支持的证据。
|
|
16
16
|
def parse_output(text, complete:)
|
|
17
17
|
text = clean(text)
|
|
18
|
-
rows, issues = [],
|
|
18
|
+
rows, issues = [], diagnostics
|
|
19
19
|
text.split(/(?=^\s*Peer:\s)/i).each do |block|
|
|
20
20
|
next if block.strip.empty? || block.strip.match?(/\A=+\z/)
|
|
21
21
|
unless block.match?(/\A\s*Peer:\s/i)
|
|
@@ -32,11 +32,11 @@ module CtxChecker
|
|
|
32
32
|
address = block[/^\s*Remote Address:\s*(\S+)/i, 1]
|
|
33
33
|
peer = address&.sub(/\A\[([^\]]+)\]:\d+\z/, '\1')
|
|
34
34
|
peer = peer.sub(/:\d+\z/, "") if peer&.match?(/\A\d+(?:\.\d+){3}:\d+\z/)
|
|
35
|
-
unless CtxParser::Values.
|
|
35
|
+
unless CtxParser::Values.ip_address?(peer)
|
|
36
36
|
issues << { code: :bgp_peer_address_missing }
|
|
37
37
|
next
|
|
38
38
|
end
|
|
39
|
-
peer = CtxParser::Values.
|
|
39
|
+
peer = CtxParser::Values.ip_address(peer)
|
|
40
40
|
state = block[/^\s*Peer status:\s*([^,\n]+)/i, 1]&.strip&.downcase
|
|
41
41
|
state = STATES.include?(state) ? state.to_sym : :unknown
|
|
42
42
|
remote_as = block[/^\s*Remote AS:\s*(\d+)\s*$/i, 1]&.to_i
|
|
@@ -17,7 +17,7 @@ module CtxChecker
|
|
|
17
17
|
# 解析 show routing protocol ospf neighbor,保留不完整或不支持的证据。
|
|
18
18
|
def parse_output(text, complete:)
|
|
19
19
|
text = clean(text)
|
|
20
|
-
rows, issues = [],
|
|
20
|
+
rows, issues = [], diagnostics
|
|
21
21
|
text.split(/(?=^\s*virtual router:)/i).each do |block|
|
|
22
22
|
unless block.match?(/\A\s*virtual router:/i)
|
|
23
23
|
# 文档规定的图例位于首个邻居前,任意前导文本不构成证据。
|
|
@@ -34,12 +34,12 @@ module CtxChecker
|
|
|
34
34
|
end
|
|
35
35
|
neighbor = block[/^\s*neighbor address:\s*(\S+)/i, 1]
|
|
36
36
|
router_id = block[/^\s*neighbor router ID:\s*(\S+)/i, 1]
|
|
37
|
-
unless CtxParser::Values.
|
|
37
|
+
unless CtxParser::Values.ip_address?(neighbor) && CtxParser::Values.ip_address?(router_id)
|
|
38
38
|
issues << { code: :ospf_neighbor_identity_missing }
|
|
39
39
|
next
|
|
40
40
|
end
|
|
41
|
-
neighbor = CtxParser::Values.
|
|
42
|
-
router_id = CtxParser::Values.
|
|
41
|
+
neighbor = CtxParser::Values.ip_address(neighbor)
|
|
42
|
+
router_id = CtxParser::Values.ip_address(router_id)
|
|
43
43
|
state = STATES.fetch(block[/^\s*status:\s*(.+)$/i, 1]&.strip&.downcase, :unknown)
|
|
44
44
|
issues << { code: :ospf_state_unknown } if state == :unknown
|
|
45
45
|
rows << { vrf: block[/^\s*virtual router:\s*(\S+)/i, 1], process_id: nil,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
|
-
require "ctx_parser/
|
|
4
|
+
require "ctx_parser/results"
|
|
5
5
|
|
|
6
6
|
module CtxChecker
|
|
7
7
|
module PaloAlto
|
|
@@ -17,7 +17,7 @@ module CtxChecker
|
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
source = clean(text)
|
|
19
19
|
rows = { temperature: [], environment: [], power: [] }
|
|
20
|
-
issues = rows.transform_values {
|
|
20
|
+
issues = rows.transform_values { diagnostics }
|
|
21
21
|
section, header, sections, section_rows = nil, nil, [], 0
|
|
22
22
|
source.each_line.with_index(1) do |raw, number|
|
|
23
23
|
line = raw.strip
|
|
@@ -88,7 +88,7 @@ module CtxChecker
|
|
|
88
88
|
if rows[:power].any? && !sections.include?("power supplies")
|
|
89
89
|
issues[:power] << { code: :power_supply_status_missing }
|
|
90
90
|
end
|
|
91
|
-
CtxParser::
|
|
91
|
+
CtxParser::Results.new(by_dimension: rows.to_h do |dimension, values|
|
|
92
92
|
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
93
93
|
[ dimension, build_result(values, complete: complete, recognized: values.any?, issues: issues.fetch(dimension), identity: identity) ]
|
|
94
94
|
end)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
|
-
require "ctx_parser/
|
|
4
|
+
require "ctx_parser/results"
|
|
5
5
|
|
|
6
6
|
module CtxChecker
|
|
7
7
|
module PaloAlto
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
# 解析 show system resources,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
source = clean(text)
|
|
19
|
-
cpus, memories, cpu_issues, memory_issues, shared_issues = [], [],
|
|
19
|
+
cpus, memories, cpu_issues, memory_issues, shared_issues = [], [], diagnostics, diagnostics, diagnostics
|
|
20
20
|
swap_lines = source.lines.grep(/\A\s*(?:(?:KiB|MiB|GiB|TiB)\s+)?Swap\s*:/)
|
|
21
21
|
process_table = false
|
|
22
22
|
source.each_line.with_index(1) do |raw, number|
|
|
@@ -33,7 +33,7 @@ module CtxChecker
|
|
|
33
33
|
shared_issues << { code: :unrecognized_resource_row, line: number }
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
|
-
CtxParser::
|
|
36
|
+
CtxParser::Results.new(by_dimension: {
|
|
37
37
|
cpu: build_result(cpus, complete: complete, recognized: cpus.any?, issues: shared_issues + cpu_issues,
|
|
38
38
|
identity: %i[component scope window_seconds]),
|
|
39
39
|
memory: build_result(memories, complete: complete, recognized: memories.any?, issues: shared_issues + memory_issues,
|
|
@@ -12,7 +12,7 @@ module CtxChecker
|
|
|
12
12
|
# 解析 show system state alarms,保留不完整或不支持的证据。
|
|
13
13
|
def parse_output(text, complete:)
|
|
14
14
|
source = clean(text)
|
|
15
|
-
rows, issues = [],
|
|
15
|
+
rows, issues = [], diagnostics
|
|
16
16
|
source.each_line.with_index(1) do |raw, number|
|
|
17
17
|
line = raw.strip
|
|
18
18
|
# 配置阈值和复制的 HA 对端状态不属于本地当前告警。
|