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,45 @@
|
|
|
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 进程 CPU 包含中断工作量,不代表 IOS-XE 的 Linux 整机 CPU。
|
|
10
|
+
class ShowProcessesCpu < CtxChecker::Parsers::Base
|
|
11
|
+
include SystemValues
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# 解析 show processes cpu,保留不完整或不支持的证据。
|
|
16
|
+
def parse_output(text, complete:)
|
|
17
|
+
records, issues = [], []
|
|
18
|
+
recognized = false
|
|
19
|
+
clean(text).each_line.with_index(1) do |source, number|
|
|
20
|
+
line = source.strip
|
|
21
|
+
next if line.empty?
|
|
22
|
+
|
|
23
|
+
if (match = /\ACPU utilization for five seconds:\s*([^%]+)%\/([^%]+)%; one minute:\s*([^%]+)%; five minutes:\s*([^%]+)%\z/i.match(line))
|
|
24
|
+
recognized = true
|
|
25
|
+
[ [ 5, match[1], match[2] ], [ 60, match[3], nil ], [ 300, match[4], nil ] ].each do |window, value, interrupt|
|
|
26
|
+
utilization = percentage(value)
|
|
27
|
+
interrupt_utilization = percentage(interrupt)
|
|
28
|
+
valid = utilization && (!interrupt || interrupt_utilization && interrupt_utilization <= utilization)
|
|
29
|
+
issues << { code: :invalid_cpu_value, line: number } unless valid
|
|
30
|
+
records << { component: "processor", scope: "ios_processes", window_seconds: window,
|
|
31
|
+
utilization: utilization, interrupt_utilization: interrupt_utilization }
|
|
32
|
+
end
|
|
33
|
+
elsif /\A(?:PID\s+Runtime|\d+\s+\d+\s+\d+\s+\d+\s+[\d.]+%\s+[\d.]+%\s+[\d.]+%\s+\d+\s+)/.match?(line)
|
|
34
|
+
next
|
|
35
|
+
else
|
|
36
|
+
issues << unparsed(number)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
build_result(records, complete: complete, recognized: recognized, issues: issues,
|
|
40
|
+
identity: %i[component scope window_seconds])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoIos
|
|
7
|
+
module Parsers
|
|
8
|
+
# 仅在 Cisco 命令语义一致时共享小型值转换方法。
|
|
9
|
+
module SystemValues
|
|
10
|
+
NUMBER = /[+-]?\d+(?:\.\d+)?/
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
# 映射组件回显状态,不将缺失解释为健康。
|
|
15
|
+
def component_status(value)
|
|
16
|
+
case value.to_s.strip.downcase
|
|
17
|
+
when "ok", "normal", "good", "green", "on", "powered-up" then :ok
|
|
18
|
+
when "warning", "minor", "yellow" then :warning
|
|
19
|
+
when "critical", "major", "red", "shutdown" then :critical
|
|
20
|
+
when "fail", "failed", "faulty", "bad", "off", "no input power", "non-operational" then :failed
|
|
21
|
+
when "absent", "not present", "not installed", "notsupported/none" then :absent
|
|
22
|
+
else :unknown
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 解析支持的数值字段,并拒绝格式错误的测量值。
|
|
27
|
+
def numeric(token)
|
|
28
|
+
return unless /\A#{NUMBER}\z/.match?(token.to_s)
|
|
29
|
+
|
|
30
|
+
value = Float(token)
|
|
31
|
+
value if value.finite?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 外部数值无效时降低证据完整性,不使类型化记录构建失败。
|
|
35
|
+
def checked_number(token, issues:, component:, nonnegative: false)
|
|
36
|
+
return if %w[N/A --].include?(token.to_s.upcase)
|
|
37
|
+
|
|
38
|
+
value = numeric(token)
|
|
39
|
+
return value if value && (!nonnegative || value >= 0)
|
|
40
|
+
|
|
41
|
+
issues << { code: :invalid_numeric_value, component: component }
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# 将利用率归一化为经过校验的百分比。
|
|
46
|
+
def percentage(token)
|
|
47
|
+
value = numeric(token)
|
|
48
|
+
value if value && value.between?(0, 100)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 保留无法解释的回显行诊断。
|
|
52
|
+
def unparsed(number) = { code: :unparsed_row, line: number }
|
|
53
|
+
|
|
54
|
+
# 读数冲突不能抹除已明确观察到的组件故障。
|
|
55
|
+
def most_severe_status(states)
|
|
56
|
+
ranks = { ok: 0, absent: 1, unknown: 2, warning: 3, failed: 4, critical: 5 }
|
|
57
|
+
states.max_by { |state| ranks.fetch(state) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ctx_parser/profile"
|
|
4
|
+
require_relative "parsers/show_interfaces"
|
|
5
|
+
require_relative "parsers/show_etherchannel_summary"
|
|
6
|
+
require_relative "parsers/show_interfaces_transceiver_detail"
|
|
7
|
+
require_relative "parsers/show_ip_bgp_summary"
|
|
8
|
+
require_relative "parsers/show_ip_ospf_neighbor"
|
|
9
|
+
require_relative "parsers/show_processes_cpu"
|
|
10
|
+
require_relative "parsers/show_memory_statistics"
|
|
11
|
+
require_relative "parsers/show_environment_all"
|
|
12
|
+
require_relative "parsers/show_clock_detail"
|
|
13
|
+
require_relative "parsers/show_ntp_status"
|
|
14
|
+
require_relative "parsers/show_facility_alarm_status"
|
|
15
|
+
require_relative "parsers/show_ip_traffic"
|
|
16
|
+
|
|
17
|
+
module CtxChecker
|
|
18
|
+
module CiscoIos
|
|
19
|
+
# IOS 与 IOS-XE 共用命令契约;型号特有语法通过解析器覆盖。
|
|
20
|
+
class Profile < CtxParser::Profile
|
|
21
|
+
CATALOG = CtxParser::CommandCatalog.new([
|
|
22
|
+
{ dimensions: %i[interfaces counters], command: "show interfaces", parser: Parsers::ShowInterfaces },
|
|
23
|
+
{ dimensions: [ :aggregation ], command: "show etherchannel summary", parser: Parsers::ShowEtherchannelSummary },
|
|
24
|
+
{ dimensions: [ :optics ], command: "show interfaces transceiver detail", parser: Parsers::ShowInterfacesTransceiverDetail },
|
|
25
|
+
{ dimensions: [ :bgp ], command: "show ip bgp summary", parser: Parsers::ShowIpBgpSummary },
|
|
26
|
+
{ dimensions: [ :ospf ], command: "show ip ospf neighbor", parser: Parsers::ShowIpOspfNeighbor },
|
|
27
|
+
{ dimensions: [ :cpu ], command: "show processes cpu", parser: Parsers::ShowProcessesCpu },
|
|
28
|
+
{ dimensions: [ :memory ], command: "show memory statistics", parser: Parsers::ShowMemoryStatistics },
|
|
29
|
+
{ dimensions: %i[temperature environment power], command: "show environment all", parser: Parsers::ShowEnvironmentAll },
|
|
30
|
+
{ dimensions: [ :clock ], command: "show clock detail", parser: Parsers::ShowClockDetail },
|
|
31
|
+
{ dimensions: [ :ntp ], command: "show ntp status", parser: Parsers::ShowNtpStatus },
|
|
32
|
+
{ dimensions: [ :alarms ], command: "show facility-alarm status", parser: Parsers::ShowFacilityAlarmStatus },
|
|
33
|
+
{ dimensions: [ :device_counters ], command: "show ip traffic", parser: Parsers::ShowIpTraffic }
|
|
34
|
+
])
|
|
35
|
+
COMMANDS = CATALOG.commands
|
|
36
|
+
PARSERS = CATALOG.parsers
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../cisco_ios/parsers/show_ip_bgp_summary"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoNxos
|
|
7
|
+
module Parsers
|
|
8
|
+
# NX-OS 在各 VRF、地址族表内独立校验已配置对等体总数。
|
|
9
|
+
class ShowBgpIpv4UnicastSummary < CiscoIos::Parsers::ShowIpBgpSummary
|
|
10
|
+
PEER_COUNTS = /\ABGP table version is \d+,\s*(.+?)\s+config peers (\d+),\s*capable peers (\d+)\z/i
|
|
11
|
+
STATISTICS = [
|
|
12
|
+
/\ADampening configured, \d+ history paths, \d+ dampened paths\z/i,
|
|
13
|
+
/\A\d+ received paths for inbound soft reconfiguration\z/i,
|
|
14
|
+
/\A\d+ identical, \d+ modified, \d+ filtered received paths using \d+ bytes\z/i
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# 缺少地址族能力时保留已观察对等体,但不声明会话健康。
|
|
20
|
+
def peer_state(text)
|
|
21
|
+
return super unless text.match?(/\A\d+\s+\(No Cap\)\z/i)
|
|
22
|
+
|
|
23
|
+
@issues << { code: :peer_capability_unavailable }
|
|
24
|
+
[ :unknown, nil ]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 配置数量覆盖全部记录;具备能力的对等体数量不等于已建立数量。
|
|
28
|
+
def parse_statistics(line, section, scope, issues, number)
|
|
29
|
+
if (match = PEER_COUNTS.match(line))
|
|
30
|
+
counts = (section[:peer_counts] ||= [])
|
|
31
|
+
issues << { code: :duplicate_peer_count, line: number } unless counts.empty?
|
|
32
|
+
counts << { configured: Integer(match[2], 10), capable: Integer(match[3], 10) }
|
|
33
|
+
if address_family(match[1]) != scope[:afi_safi]
|
|
34
|
+
issues << { code: :conflicting_address_family, line: number }
|
|
35
|
+
end
|
|
36
|
+
elsif line.match?(/\ABGP table version\b.*\b(?:config|capable) peers\b/i)
|
|
37
|
+
issues << { code: :invalid_peer_count, line: number }
|
|
38
|
+
else
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 仅当能力计数同样一致时,零数量才能证明空表。
|
|
45
|
+
def section_empty?(section)
|
|
46
|
+
super || section[:peer_counts] == [ { configured: 0, capable: 0 } ]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 校验每条总数声明,避免重复或矛盾总数掩盖记录缺失。
|
|
50
|
+
def validate_section(section, issues)
|
|
51
|
+
super
|
|
52
|
+
Array(section[:peer_counts]).each do |counts|
|
|
53
|
+
unless counts[:configured] == section[:count]
|
|
54
|
+
issues << { code: :count_mismatch, expected: counts[:configured], observed: section[:count] }
|
|
55
|
+
end
|
|
56
|
+
if counts[:capable] > counts[:configured]
|
|
57
|
+
issues << { code: :invalid_capable_peer_count, expected: counts[:configured], observed: counts[:capable] }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 可选路由统计仅接受文档明确的完整格式。
|
|
63
|
+
def auxiliary?(line)
|
|
64
|
+
super || STATISTICS.any? { |pattern| pattern.match?(line) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../cisco_ios/parsers/show_clock_detail"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoNxos
|
|
7
|
+
module Parsers
|
|
8
|
+
# NX-OS 使用相同的时钟正文,不含 IOS 的 detail 关键字。
|
|
9
|
+
class ShowClock < CtxChecker::CiscoIos::Parsers::ShowClockDetail
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../cisco_ios/parsers/system_values"
|
|
5
|
+
require "ctx_parser/result_set"
|
|
6
|
+
|
|
7
|
+
module CtxChecker
|
|
8
|
+
module CiscoNxos
|
|
9
|
+
module Parsers
|
|
10
|
+
# 每个环境章节分别产生类型化组件观测。
|
|
11
|
+
class ShowEnvironment < CtxChecker::Parsers::Base
|
|
12
|
+
include CtxChecker::CiscoIos::Parsers::SystemValues
|
|
13
|
+
HEADERS = /\A(?:[- ]+|\(?Watts\s*\)?(?:\s+\(?Watts\s*\)?)*|\(?Celsius\)?(?:\s+\(?Celsius\)?)*|Module\s+Sensor|Clock\s+Model|Fan\s+Model|Power\s+Actual|Supply\s+Model|Actual\s+Power|Module\s+Model|Output\s+Capacity|N\/A - Per module|Voltage:)/i
|
|
14
|
+
POWER_VALUE = /(?:#{NUMBER}\s*W|N\/A(?:\s*W)?|--)/i
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# 解析 show environment,保留不完整或不支持的证据。
|
|
19
|
+
def parse_output(text, complete:)
|
|
20
|
+
@rows = { temperature: [], environment: [], power: [] }
|
|
21
|
+
@problems = @rows.transform_values { [] }
|
|
22
|
+
@section = nil
|
|
23
|
+
@section_size = 0
|
|
24
|
+
@power_columns = nil
|
|
25
|
+
clean(text).each_line.with_index(1) do |source, number|
|
|
26
|
+
line = source.strip
|
|
27
|
+
next if line.empty?
|
|
28
|
+
next if section_line(line) || header_line(line)
|
|
29
|
+
next if @section == :fan && /\AFan Zone Speed:\s*Zone\s+\d+:/i.match?(line)
|
|
30
|
+
next if parse_temperature(line) || parse_component(line) || parse_power(line)
|
|
31
|
+
next if @section == :summary && /\A(?:Power Supply redundancy|Total Power |Total Grid|Cumulative Power)/i.match?(line)
|
|
32
|
+
|
|
33
|
+
dimension = { temperature: :temperature, fan: :environment, clock: :environment, power: :power, module: :environment, summary: :power }[@section]
|
|
34
|
+
(dimension ? [ @problems[dimension] ] : @problems.values).each { |issues| issues << unparsed(number) }
|
|
35
|
+
end
|
|
36
|
+
finish_section
|
|
37
|
+
CtxParser::ResultSet.new(results: @rows.to_h do |dimension, rows|
|
|
38
|
+
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
39
|
+
[ dimension, build_result(rows, complete: complete, recognized: rows.any?, issues: @problems[dimension], identity: identity) ]
|
|
40
|
+
end)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# 识别环境章节内的行类型。
|
|
44
|
+
def section_line(line)
|
|
45
|
+
section = case line
|
|
46
|
+
when /\ATemperature:\z/i then :temperature
|
|
47
|
+
when /\AFan:\z/i then :fan
|
|
48
|
+
when /\AClock:\z/i then :clock
|
|
49
|
+
when /\APower Supply:\z/i then :power
|
|
50
|
+
when /\APower Usage Summary:\z/i then :summary
|
|
51
|
+
when /\AModule\s+Model.*Status/i then :module
|
|
52
|
+
else return false
|
|
53
|
+
end
|
|
54
|
+
finish_section
|
|
55
|
+
@section = section
|
|
56
|
+
@section_size = section_rows.size
|
|
57
|
+
@power_columns = nil if section == :power
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 行被截断时,不能将已声明的输入列误当作额定容量。
|
|
62
|
+
def header_line(line)
|
|
63
|
+
if @section == :power && /\ASupply\s+Model\s+Output\b/i.match?(line)
|
|
64
|
+
@power_columns = /\bInput\b/i.match?(line) ? 3 : 2
|
|
65
|
+
end
|
|
66
|
+
HEADERS.match?(line)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 选择属于当前环境章节的数据行。
|
|
70
|
+
def section_rows
|
|
71
|
+
dimension = { temperature: :temperature, fan: :environment, clock: :environment, power: :power, module: :environment }[@section]
|
|
72
|
+
dimension ? @rows[dimension] : []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# 独立完成当前环境章节,不影响其他章节的结果。
|
|
76
|
+
def finish_section
|
|
77
|
+
return unless @section && @section != :summary && section_rows.size == @section_size
|
|
78
|
+
|
|
79
|
+
dimension = %i[fan clock module].include?(@section) ? :environment : @section
|
|
80
|
+
@problems[dimension] << { code: :empty_environment_section, scope: @section.to_s }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 归一化温度传感器及其回显阈值。
|
|
84
|
+
def parse_temperature(line)
|
|
85
|
+
return false unless @section == :temperature
|
|
86
|
+
|
|
87
|
+
match = /\A(\S+)\s+(.+?)\s+(#{NUMBER}|N\/A)\s+(#{NUMBER}|N\/A)\s+(#{NUMBER}|N\/A)\s+(.+)\z/i.match(line)
|
|
88
|
+
return false unless match
|
|
89
|
+
|
|
90
|
+
component = "#{match[1]}/#{match[2]}"
|
|
91
|
+
high_alarm, high_warning, value = match.captures[2, 3].map do |token|
|
|
92
|
+
checked_number(token, issues: @problems[:temperature], component: component)
|
|
93
|
+
end
|
|
94
|
+
# NX-OS 使用 -127 表示传感器阈值不可用或不适用。
|
|
95
|
+
high_alarm = nil if high_alarm == -127
|
|
96
|
+
high_warning = nil if high_warning == -127
|
|
97
|
+
@rows[:temperature] << { component: component, value: value, low_alarm: nil, low_warning: nil,
|
|
98
|
+
high_warning: high_warning, high_alarm: high_alarm, high_comparison: :gt, status: component_status(match[6]) }
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# 归一化单个环境组件及其运行状态。
|
|
103
|
+
def parse_component(line)
|
|
104
|
+
return false unless %i[fan clock].include?(@section)
|
|
105
|
+
|
|
106
|
+
if (match = /\AFan Air Filter\s*:\s*(.+)\z/i.match(line))
|
|
107
|
+
@rows[:environment] << { component: "fan air filter", kind: :unknown,
|
|
108
|
+
status: match[1].casecmp?("NotSupported") ? :absent : component_status(match[1]) }
|
|
109
|
+
elsif @section == :fan && (match = /\A(\S+)\s+\S+\s+\S+(?:\s+(?:front-to-back|back-to-front))?\s+(.+)\z/i.match(line))
|
|
110
|
+
@rows[:environment] << { component: "fan #{match[1]}", kind: :fan, status: component_status(match[2]) }
|
|
111
|
+
elsif (match = /\A(\S+)\s+.+?\s+(Ok|Normal|Good|Fail|Failed|Faulty|Absent|Not Present|NotSupported\/None)\z/i.match(line))
|
|
112
|
+
@rows[:environment] << { component: "#{@section} #{match[1]}", kind: @section, status: component_status(match[2]) }
|
|
113
|
+
else
|
|
114
|
+
return false
|
|
115
|
+
end
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 归一化电源测量值及回显状态。
|
|
120
|
+
def parse_power(line)
|
|
121
|
+
return false unless %i[power module].include?(@section)
|
|
122
|
+
|
|
123
|
+
match = /\A(\S+)\s+\S+\s+(#{POWER_VALUE})\s+(#{POWER_VALUE})(?:\s+(#{POWER_VALUE}))?\s+(.+)\z/i.match(line)
|
|
124
|
+
return false unless match
|
|
125
|
+
return false if @section == :module && match[4]
|
|
126
|
+
return false if @section == :power && @power_columns && @power_columns != (match[4] ? 3 : 2)
|
|
127
|
+
|
|
128
|
+
state = component_status(match[5])
|
|
129
|
+
if @section == :module
|
|
130
|
+
@rows[:environment] << { component: "module #{match[1]}", kind: :module, status: state }
|
|
131
|
+
else
|
|
132
|
+
component = "power supply #{match[1]}"
|
|
133
|
+
# 部分平台在输出与容量之间插入 Actual Input 列。
|
|
134
|
+
watts, capacity = [ match[2], match[4] || match[3] ].map do |token|
|
|
135
|
+
checked_number(token.sub(/W\z/i, "").strip, issues: @problems[:power], component: component, nonnegative: true)
|
|
136
|
+
end
|
|
137
|
+
if match[4]
|
|
138
|
+
checked_number(match[3].sub(/W\z/i, "").strip, issues: @problems[:power], component: component, nonnegative: true)
|
|
139
|
+
end
|
|
140
|
+
@rows[:power] << { component: component, kind: :power_supply, status: state,
|
|
141
|
+
present: state == :absent ? false : nil, watts: watts, capacity_watts: capacity }
|
|
142
|
+
end
|
|
143
|
+
true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# NX-OS 拼写差异仅在本平台处理;未报告的风扇状态保持未知。
|
|
147
|
+
def component_status(value)
|
|
148
|
+
case value.to_s.strip.downcase
|
|
149
|
+
when "shut" then :critical
|
|
150
|
+
when "failure", "fail/shutdown", "powered-dn" then :failed
|
|
151
|
+
else super
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../cisco_ios/parsers/show_interfaces"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoNxos
|
|
7
|
+
module Parsers
|
|
8
|
+
# NX-OS 与 IOS 共享标签式计数器,但单独提供管理状态。
|
|
9
|
+
class ShowInterface < CiscoIos::Parsers::ShowInterfaces
|
|
10
|
+
PLATFORM = :cisco_nxos
|
|
11
|
+
HEADER = /\A(\S+) is (administratively down|up|down|reset|deleted)(?:\s*\([^)]*\))?(?:, line protocol is (up|down)(?: \([^)]*\))?)?(?:, autostate (?:enabled|disabled))?\s*\z/i
|
|
12
|
+
AUXILIARY = /\A(?:\d+ seconds? (?:input|output) rate\b|Auto-mdix is (?:turned )?(?:on|off)\z|Rate mode is \S+\z|EEE \(efficient-ethernet\)\s*:|admin fec state is \S+, oper fec state is \S+\z|L3 Switched:\z|(?:input|ucast): \d+ pkts, \d+ bytes - (?:output|mcast): \d+ pkts, \d+ bytes\z)/i
|
|
13
|
+
DETAIL_METADATA = /\A(?:\d+ broadcast packets \d+ bytes|\d+ multicast frames \d+ compressed|No members|ucast: \d+ pkts, \d+ bytes)\z/i
|
|
14
|
+
COUNTERS = CiscoIos::Parsers::ShowInterfaces::COUNTERS.merge(
|
|
15
|
+
stomped_crc_errors: /(?<!\S)(\d+) Stomped\s+CRC(?=\s|\z)/i,
|
|
16
|
+
out_carrier_errors: /(?<!\S)(\d+) out_carrier_errors(?=\s|\z)/i).freeze
|
|
17
|
+
COUNTER_LABELS = CiscoIos::Parsers::ShowInterfaces::COUNTER_LABELS.merge(
|
|
18
|
+
stomped_crc_errors: /\bStomped\s+CRC\b/i, out_carrier_errors: /\bout_carrier_errors\b/i).freeze
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# NX-OS 的 SVI 头可附带 autostate,不改变协议状态语义。
|
|
23
|
+
def interface_header(line) = HEADER.match(line)
|
|
24
|
+
# 声明当前平台支持的接口错误与丢弃计数模式。
|
|
25
|
+
def counter_patterns = COUNTERS
|
|
26
|
+
# 将平台计数器标签映射到归一化指标名称。
|
|
27
|
+
def counter_labels = COUNTER_LABELS
|
|
28
|
+
# 识别包含接口计数测量值的数据行。
|
|
29
|
+
def counter_line?(line) = COUNTER_LABELS.values.any? { |pattern| pattern.match?(line) } || super
|
|
30
|
+
|
|
31
|
+
# CRC 总数已包含 stomped 帧,该子集应独立保留。
|
|
32
|
+
def counter_source(line, metric)
|
|
33
|
+
metric == :crc_errors ? line.gsub(/\bStomped\s+CRC\b/i, "stomped_checksum") : line
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# NX-OS 在 SVI 运行状态头中报告明确的 shutdown 原因。
|
|
37
|
+
def interface_record(match)
|
|
38
|
+
super.tap do |record|
|
|
39
|
+
record[:admin_status] = :down if match[0].match?(/\A\S+ is down\s*\(Administratively down\)/i)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# 保留物理成员端口回显的聚合归属关系。
|
|
44
|
+
def interface_detail(record, line)
|
|
45
|
+
if (match = /\ABelongs to ((?:Po|port-channel)\d+)\z/i.match(line))
|
|
46
|
+
record[:parent] = normalize_name(match[1])
|
|
47
|
+
elsif record[:kind] == :aggregate && (match = /\AvPC Status: (Up|Down), vPC number: \d+(?: \[packets forwarded via vPC peer-link\])?\z/i.match(line))
|
|
48
|
+
return record[:oper_status] == match[1].downcase.to_sym
|
|
49
|
+
elsif /\Amgmt\d+\z/i.match?(record[:name]) && /\AManagement transceiver: (?:Absent|Present)\z/i.match?(line)
|
|
50
|
+
(record[:management_metadata] ||= []) << :transceiver
|
|
51
|
+
elsif /\Amgmt\d+\z/i.match?(record[:name]) && /\AActive connector: (?:RJ45|SFP)\z/i.match?(line)
|
|
52
|
+
(record[:management_metadata] ||= []) << :connector
|
|
53
|
+
else
|
|
54
|
+
return false
|
|
55
|
+
end
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 部分管理接口表以连接器信息结尾,且仅提供包总数。
|
|
60
|
+
def complete_counter_coverage(record, counters, issues)
|
|
61
|
+
metadata = record.delete(:management_metadata)
|
|
62
|
+
return super unless metadata == %i[transceiver connector] && counters.empty? && issues.empty?
|
|
63
|
+
|
|
64
|
+
%i[in_errors out_errors crc_errors].map do |metric|
|
|
65
|
+
{ interface: record[:name], metric: metric, value: nil, discontinuity: nil }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 仅忽略已识别的描述字段;新出现的状态语法使结果保持部分完整。
|
|
70
|
+
def auxiliary?(line)
|
|
71
|
+
return false if line.match?(/\Aadmin state is\b/i)
|
|
72
|
+
|
|
73
|
+
AUXILIARY.match?(line) || DETAIL_METADATA.match?(line) || super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module CiscoNxos
|
|
8
|
+
module Parsers
|
|
9
|
+
# NX-OS DOM 按接口及可选光通道组织测量值。
|
|
10
|
+
class ShowInterfaceTransceiverDetails < CtxChecker::Parsers::Base
|
|
11
|
+
POWER = /([+-]?\d+(?:\.\d+)?\s+dBm|(?:N\/A|NA|--)(?:\s+dBm)?)/i
|
|
12
|
+
MARKERS = { "++" => :high_alarm, "+" => :high_warning, "--" => :low_alarm, "-" => :low_warning }.freeze
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 保留 DOM 不可用和模块缺失状态,不虚构光功率。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
text = clean(text)
|
|
19
|
+
rows, issues, devices = [], [], []
|
|
20
|
+
current, lane, header, lane_valid = nil, nil, false, true
|
|
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?(/\A(?:Eth(?:ernet)?|mgmt)\d[\d\/.]*\z/i)
|
|
26
|
+
current = { interface: CtxChecker::Parsers::InterfaceNames.normalize(line, platform: :cisco_nxos),
|
|
27
|
+
applicable: nil, present: nil, dom_supported: nil, lanes: [], presence_states: [], dom_unsupported: false }
|
|
28
|
+
devices << current
|
|
29
|
+
lane, header, lane_valid = nil, false, true
|
|
30
|
+
elsif current && (match = /\Atransceiver is (not )?present\z/i.match(line))
|
|
31
|
+
current[:present] = match[1].nil?
|
|
32
|
+
current[:presence_states] << current[:present]
|
|
33
|
+
elsif current && line.match?(/\Atransceiver is not applicable\z/i)
|
|
34
|
+
current[:applicable] = false
|
|
35
|
+
current[:dom_supported] = false
|
|
36
|
+
elsif current && line.match?(/\ADOM is not supported\z/i)
|
|
37
|
+
current[:dom_supported] = false
|
|
38
|
+
current[:dom_unsupported] = true
|
|
39
|
+
elsif current && (match = /\ALane Number\s*:\s*(\d+)(?:\s+Network Lane)?\z/i.match(line))
|
|
40
|
+
lane = Integer(match[1], 10)
|
|
41
|
+
current[:lanes] << lane
|
|
42
|
+
header, lane_valid = false, true
|
|
43
|
+
elsif current && line.match?(/\ALane Number\b/i)
|
|
44
|
+
lane, header, lane_valid = nil, false, false
|
|
45
|
+
issues << { code: :invalid_optical_lane, line: number, interface: current[:interface] }
|
|
46
|
+
elsif current && line.match?(/\AMeasurement\s+High\s+Low\s+High\s+Low\z/i)
|
|
47
|
+
header = true
|
|
48
|
+
elsif current && (match = /\A(Tx Power|Rx Power)\s+(.+)\z/i.match(line))
|
|
49
|
+
next unless lane_valid
|
|
50
|
+
|
|
51
|
+
row = measurement(match[1], match[2], current, lane)
|
|
52
|
+
if row
|
|
53
|
+
rows << row
|
|
54
|
+
current[:dom_supported] = true
|
|
55
|
+
issues << { code: :missing_optical_header, line: number } unless header
|
|
56
|
+
else
|
|
57
|
+
issues << { code: :unparsed_optical_measurement, line: number }
|
|
58
|
+
end
|
|
59
|
+
elsif current && auxiliary?(line)
|
|
60
|
+
next
|
|
61
|
+
else
|
|
62
|
+
issues << { code: :unparsed_row, line: number }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
devices.each { |device| complete_measurements(rows, device, issues) }
|
|
66
|
+
build_result(rows, complete: complete, recognized: devices.any?, issues: issues, identity: %i[interface lane metric])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 解析支持的传感器测量值及回显单位。
|
|
70
|
+
def measurement(label, values, device, lane)
|
|
71
|
+
match = /\A#{POWER}\s*(\+\+|\+|--|-)?\s+#{POWER}\s+#{POWER}\s+#{POWER}\s+#{POWER}\z/i.match(values)
|
|
72
|
+
return unless match
|
|
73
|
+
|
|
74
|
+
current, marker, high_alarm, low_alarm, high_warning, low_warning = match.captures
|
|
75
|
+
value = decimal(current)
|
|
76
|
+
base_record(device, lane, label.downcase.start_with?("tx") ? :tx_power : :rx_power).merge(
|
|
77
|
+
value: value, high_alarm: decimal(high_alarm), low_alarm: decimal(low_alarm),
|
|
78
|
+
high_warning: decimal(high_warning), low_warning: decimal(low_warning),
|
|
79
|
+
alarm: marker ? MARKERS.fetch(marker) : (value.nil? ? :unknown : :normal), dom_supported: true)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 即使配对指标解析成功,缺失指标仍须明确保留。
|
|
83
|
+
def complete_measurements(rows, device, issues)
|
|
84
|
+
device_rows = rows.select { |row| row[:interface] == device[:interface] }
|
|
85
|
+
if device[:applicable] == false && (!device[:presence_states].empty? || device[:lanes].any? || device_rows.any?)
|
|
86
|
+
issues << { code: :contradictory_optical_applicability, interface: device[:interface] }
|
|
87
|
+
device[:applicable] = nil
|
|
88
|
+
device_rows.each { |row| row[:applicable] = nil }
|
|
89
|
+
end
|
|
90
|
+
if device[:presence_states].uniq.size > 1
|
|
91
|
+
issues << { code: :contradictory_transceiver_presence, interface: device[:interface] }
|
|
92
|
+
end
|
|
93
|
+
if device[:dom_unsupported] && rows.any? { |row| row[:interface] == device[:interface] && !row[:value].nil? }
|
|
94
|
+
issues << { code: :contradictory_dom_support, interface: device[:interface] }
|
|
95
|
+
end
|
|
96
|
+
lanes = device[:lanes].empty? ? [ nil ] : device[:lanes].uniq
|
|
97
|
+
lanes.each do |lane|
|
|
98
|
+
%i[tx_power rx_power].each do |metric|
|
|
99
|
+
next if rows.any? { |row| row.values_at(:interface, :lane, :metric) == [ device[:interface], lane, metric ] }
|
|
100
|
+
|
|
101
|
+
rows << base_record(device, lane, metric)
|
|
102
|
+
unless device[:present] == false || device[:dom_supported] == false
|
|
103
|
+
issues << { code: :optical_measurement_missing, interface: device[:interface], lane: lane, metric: metric }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
if device[:present].nil? && device[:applicable] != false
|
|
108
|
+
issues << { code: :transceiver_presence_unknown, interface: device[:interface] }
|
|
109
|
+
end
|
|
110
|
+
if device[:present] == false && rows.any? { |row| row[:interface] == device[:interface] && !row[:value].nil? }
|
|
111
|
+
issues << { code: :contradictory_transceiver_presence, interface: device[:interface] }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# 构建同一资源各测量值共用的身份字段。
|
|
116
|
+
def base_record(device, lane, metric)
|
|
117
|
+
{ interface: device[:interface], lane: lane, metric: metric, value: nil, unit: :dbm,
|
|
118
|
+
low_alarm: nil, low_warning: nil, high_warning: nil, high_alarm: nil, alarm: :unknown,
|
|
119
|
+
applicable: device[:applicable], present: device[:present], dom_supported: device[:dom_supported] }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 解析有限小数,不将占位符转换为零。
|
|
123
|
+
def decimal(value)
|
|
124
|
+
value = value.sub(/\s+dBm\z/i, "")
|
|
125
|
+
return unless value.match?(/\A[+-]?\d+(?:\.\d+)?\z/)
|
|
126
|
+
|
|
127
|
+
parsed = Float(value)
|
|
128
|
+
return parsed if parsed.finite?
|
|
129
|
+
|
|
130
|
+
@issues << { code: :invalid_optical_value }
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# 识别说明性回显,不将其作为数据记录。
|
|
135
|
+
def auxiliary?(line)
|
|
136
|
+
line.match?(/\A(?:(?:type|name|part number|revision|serial number|nominal bitrate|cisco [\w ]+|connector type|encoding|host lane count|power class) is\b|Link length supported |(?:SFP|QSFP) Detail Diagnostics Information |Current\s+Alarms\s+Warnings\z|(?:Temperature|Voltage|Current)\s+(?:[+-]?\d|N\/A)|Transmit Fault Count\s*=\s*\d+\z|Note:|\+\+ :|\*\* :|\+ :|\- :|\-\- :)/i)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../cisco_ios/parsers/show_ip_ospf_neighbor"
|
|
4
|
+
|
|
5
|
+
module CtxChecker
|
|
6
|
+
module CiscoNxos
|
|
7
|
+
module Parsers
|
|
8
|
+
# NX-OS 复用已校验的 OSPFv2 行,同时保留分进程总数与明确的 VRF 作用域。
|
|
9
|
+
class ShowIpOspfNeighbors < CiscoIos::Parsers::ShowIpOspfNeighbor
|
|
10
|
+
PLATFORM = :cisco_nxos
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|