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,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 本地成员选中状态不能从远端 LACP 记录推断。
|
|
10
|
+
class DisplayLinkAggregationVerbose < CtxChecker::Parsers::Base
|
|
11
|
+
GROUP = /\A(?:Bridge-Aggregation|Route-Aggregation|BAGG|RAGG)\d+\z/i
|
|
12
|
+
LOCAL_HEADER = /\APort\s+Status\s+Priority(?:\s+(Index))?\s+Oper-Key(?:\s+(Flag))?\z/i
|
|
13
|
+
MEMBER = /\A([A-Za-z][A-Za-z-]*\d+(?:[\/.]\d+)*)(?:\(([AMR])\))?\s+([SUI])\s+(\d+)\s+(\d+)(?:\s+(\d+))?(?:\s+(\{[A-H]*\}))?\z/
|
|
14
|
+
METADATA = /\A(?:-+|Loadsharing Type:|Port Status:|Port:|Flags:|[A-H] --|Creation Mode:|Aggregation Mode:|System ID:)/
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# 按聚合组及声明的列布局限定各本地表的作用域。
|
|
19
|
+
def parse_output(text, complete:)
|
|
20
|
+
text = clean(text)
|
|
21
|
+
if /\AInfo:\s+No link aggregation group defined at present\.\z/i.match?(text.strip)
|
|
22
|
+
return build_result([], complete: complete, recognized: true, issues: [ { code: :aggregation_not_configured } ])
|
|
23
|
+
end
|
|
24
|
+
rows, issues, group, remote, groups, columns, tables = [], [], nil, false, {}, nil, []
|
|
25
|
+
text.each_line do |raw|
|
|
26
|
+
line = raw.strip
|
|
27
|
+
if (match = /\AAggregate Interface:\s*(.*)\z/i.match(line))
|
|
28
|
+
group, remote, columns = nil, false, nil
|
|
29
|
+
if GROUP.match?(match[1])
|
|
30
|
+
group = CtxChecker::Parsers::InterfaceNames.normalize(match[1], platform: :h3c)
|
|
31
|
+
issues << { code: :duplicate_aggregation_group, group: group } if groups.key?(group)
|
|
32
|
+
groups[group] ||= 0
|
|
33
|
+
else
|
|
34
|
+
issues << { code: :invalid_aggregation_group }
|
|
35
|
+
end
|
|
36
|
+
elsif line == "Remote:"
|
|
37
|
+
remote = true
|
|
38
|
+
columns = nil
|
|
39
|
+
elsif line == "Local:"
|
|
40
|
+
remote = false
|
|
41
|
+
columns = nil
|
|
42
|
+
elsif !remote && (match = LOCAL_HEADER.match(line))
|
|
43
|
+
columns = { indexed: !match[1].nil?, flags: !match[2].nil?, group: group, count: 0 }
|
|
44
|
+
tables << columns
|
|
45
|
+
elsif !remote && (record = local_member(line, group: group, columns: columns))
|
|
46
|
+
rows << record
|
|
47
|
+
groups[group] += 1
|
|
48
|
+
columns[:count] += 1
|
|
49
|
+
elsif !remote && line.match?(/\A[A-Za-z][A-Za-z-]*\d+(?:[\/.]\d+)*(?:\([^\s]*\))?\s/)
|
|
50
|
+
issues << { code: :invalid_aggregation_member }
|
|
51
|
+
elsif !remote && !line.empty? && !METADATA.match?(line) && !/\AManagement VLANs:\s*(?:None|\d+(?:-\d+)?(?:,\s*\d+(?:-\d+)?)*)\z/i.match?(line)
|
|
52
|
+
issues << { code: :unrecognized_aggregation_line }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
groups.each { |name, count| issues << { code: :aggregation_members_missing, group: name } if count.zero? }
|
|
56
|
+
tables.each { |table| issues << { code: :aggregation_members_missing, group: table[:group] } if table[:count].zero? }
|
|
57
|
+
build_result(rows, complete: complete, recognized: groups.any?, issues: issues, identity: %i[group member])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 仅在身份匹配前移除已知端口角色标记,不修改状态文本。
|
|
61
|
+
def local_member(line, group:, columns:)
|
|
62
|
+
return unless group && columns
|
|
63
|
+
match = MEMBER.match(line)
|
|
64
|
+
return unless match && columns[:indexed] == !match[6].nil? && columns[:flags] == !match[7].nil?
|
|
65
|
+
|
|
66
|
+
{ group: group, member: CtxChecker::Parsers::InterfaceNames.normalize(match[1], platform: :h3c),
|
|
67
|
+
member_status: { "S" => :active, "U" => :unselected, "I" => :unselected }.fetch(match[3]), oper_status: :unknown }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "system_values"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 应用内存使用量必须来自同一物理内存池中经过确认的缓存调整行。
|
|
10
|
+
class DisplayMemory < CtxChecker::Parsers::Base
|
|
11
|
+
include SystemValues
|
|
12
|
+
LEGACY_FIELDS = { "System Total Memory(bytes)" => :total, "Total Used Memory(bytes)" => :used, "Used Rate" => :rate }.freeze
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# 解析 display memory,保留不完整或不支持的证据。
|
|
17
|
+
def parse_output(text, complete:)
|
|
18
|
+
source = clean(text)
|
|
19
|
+
return legacy_memory(source, complete: complete) if /^\s*System Total Memory\(bytes\):/.match?(source)
|
|
20
|
+
|
|
21
|
+
rows, contexts, sections = [], [], []
|
|
22
|
+
context, scope, unit, columns, current, section = nil, "physical", nil, nil, nil, nil
|
|
23
|
+
source.each_line do |raw|
|
|
24
|
+
line = raw.strip
|
|
25
|
+
if /\A(?:Container memory statistics|Memory statistics|The statistics about memory).*measured in (KB|bytes)(?:\(Available\))?:\z/i.match?(line)
|
|
26
|
+
unit = line.match(/measured in (KB|bytes)/i)[1].casecmp?("KB") ? 1024 : 1
|
|
27
|
+
scope = line.start_with?("Container") ? "container" : "physical"
|
|
28
|
+
context, columns, current = nil, nil, nil
|
|
29
|
+
sections << (section = { scope: scope, samples: 0 })
|
|
30
|
+
elsif (match = /\A((?:Chassis \d+ )?Slot \d+(?: CPU \d+)?):\z/i.match(line))
|
|
31
|
+
contexts << (context = { component: scope_name(match[1]), scope: scope, samples: 0 })
|
|
32
|
+
columns, current = nil, nil
|
|
33
|
+
elsif /\ATotal\s+Used\s+Free\b/.match?(line)
|
|
34
|
+
columns = line.split
|
|
35
|
+
elsif (match = /\AMem:\s+(\S+)\s+(\S+)\s+(\S+)(?:\s+.*)?\z/.match(line))
|
|
36
|
+
if context && unit
|
|
37
|
+
values = line.delete_prefix("Mem:").split
|
|
38
|
+
if columns.nil? || values.size != columns.size
|
|
39
|
+
@issues << { code: :incomplete_memory_row, component: context[:component] }
|
|
40
|
+
end
|
|
41
|
+
record = memory_record(context[:component], scope, unit, match.captures)
|
|
42
|
+
rows << record
|
|
43
|
+
current = { record: record, raw: record.dup, values: (columns || []).zip(values).to_h, adjustments: 0 }
|
|
44
|
+
context[:samples] += 1
|
|
45
|
+
section[:samples] += 1 if section
|
|
46
|
+
else
|
|
47
|
+
@issues << { code: :missing_memory_scope_or_unit }
|
|
48
|
+
end
|
|
49
|
+
elsif (match = /\A-\/\+ Buffers\/Cache:\s+(\S+)\s+(\S+)\z/i.match(line))
|
|
50
|
+
adjust_cache(current, match.captures, unit)
|
|
51
|
+
elsif !line.empty? && !/\A(?:Swap:|LowMem:|HighMem:)/i.match?(line)
|
|
52
|
+
@issues << { code: :unrecognized_memory_line }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
contexts.each do |entry|
|
|
56
|
+
@issues << { code: :missing_memory_sample, component: entry[:component], scope: entry[:scope] } if entry[:samples].zero?
|
|
57
|
+
end
|
|
58
|
+
sections.each do |entry|
|
|
59
|
+
@issues << { code: :missing_memory_section, scope: entry[:scope] } if entry[:samples].zero?
|
|
60
|
+
end
|
|
61
|
+
build_result(rows, complete: complete, recognized: rows.any?, identity: %i[component scope])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 旧交换机仅报告物理内存使用量,不能证明缓存调整后的应用使用量。
|
|
65
|
+
def legacy_memory(source, complete:)
|
|
66
|
+
values, seen = {}, {}
|
|
67
|
+
source.each_line do |raw|
|
|
68
|
+
line = raw.strip
|
|
69
|
+
next if line.empty?
|
|
70
|
+
|
|
71
|
+
key, value = line.split(/:\s*/, 2)
|
|
72
|
+
field = LEGACY_FIELDS[key]
|
|
73
|
+
unless field && value
|
|
74
|
+
@issues << { code: :unrecognized_memory_line }
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
if seen[field]
|
|
78
|
+
values[field] = nil
|
|
79
|
+
@issues << { code: :duplicate_memory_field, field: field, component: "system" }
|
|
80
|
+
else
|
|
81
|
+
values[field] = if field == :rate
|
|
82
|
+
match = /\A(#{NUMBER})%\z/i.match(value)
|
|
83
|
+
measurement(match && match[1], field: :utilization, component: "system", percent: true)
|
|
84
|
+
else
|
|
85
|
+
count(value, field: :memory, component: "system")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
seen[field] = true
|
|
89
|
+
end
|
|
90
|
+
total, used, rate = values.values_at(:total, :used, :rate)
|
|
91
|
+
valid = total && total.positive? && used && used <= total && rate && (rate - used.fdiv(total) * 100).abs < 1
|
|
92
|
+
@issues << { code: :inconsistent_memory_totals, component: "system" } unless valid
|
|
93
|
+
row = { component: "system", scope: "physical", utilization: valid ? used.fdiv(total) * 100 : nil,
|
|
94
|
+
total_bytes: total, used_bytes: used, free_bytes: nil, basis: :used_including_cache }
|
|
95
|
+
build_result([ row ], complete: complete, recognized: true, identity: %i[component scope])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 由兼容的容量与使用量测量计算内存利用率。
|
|
99
|
+
def memory_record(component, scope, unit, values)
|
|
100
|
+
total, used, free = values.map { |value| count(value, field: :memory, component: component) }
|
|
101
|
+
valid = total && total.positive? && used && free && used <= total && free <= total && (scope == "container" || used + free == total)
|
|
102
|
+
@issues << { code: :inconsistent_memory_totals, component: component } unless valid
|
|
103
|
+
{ component: component, scope: scope, utilization: valid ? used.fdiv(total) * 100 : nil,
|
|
104
|
+
total_bytes: total && total * unit, used_bytes: used && used * unit, free_bytes: scope == "physical" && free ? free * unit : nil,
|
|
105
|
+
basis: scope == "container" ? :used_total : :used_including_cache }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# 仅在平台明确报告时扣除可回收内存。
|
|
109
|
+
def adjust_cache(current, values, unit)
|
|
110
|
+
unless current && current[:record][:scope] == "physical"
|
|
111
|
+
@issues << { code: :unscoped_memory_cache_adjustment }
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
row, raw, fields = current.values_at(:record, :raw, :values)
|
|
115
|
+
current[:adjustments] += 1
|
|
116
|
+
used, free = values.map { |value| count(value, field: :memory, component: row[:component]) }
|
|
117
|
+
buffers, cached = fields.values_at("Buffers", "Cached").map { |value| count(value, field: :memory, component: row[:component]) }
|
|
118
|
+
valid = raw[:utilization] && used && free && buffers && cached && current[:adjustments] == 1 &&
|
|
119
|
+
(used + free) * unit == raw[:total_bytes] && used * unit == raw[:used_bytes] - (buffers + cached) * unit &&
|
|
120
|
+
free * unit == raw[:free_bytes] + (buffers + cached) * unit
|
|
121
|
+
unless valid
|
|
122
|
+
row.replace(raw.merge(utilization: nil, basis: :unknown))
|
|
123
|
+
@issues << { code: :invalid_memory_cache_adjustment, component: row[:component] }
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
row.merge!(used_bytes: used * unit, available_bytes: free * unit,
|
|
127
|
+
utilization: (used * unit).fdiv(raw[:total_bytes]) * 100, basis: :used_minus_cache)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "system_values"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 系统对等体才是同步服务器;reference ID 可能是本地时钟或 IPv6 哈希。
|
|
10
|
+
class DisplayNtpServiceStatus < CtxChecker::Parsers::Base
|
|
11
|
+
include SystemValues
|
|
12
|
+
FIELDS = { "clock status" => :synchronized, "clock stratum" => :stratum,
|
|
13
|
+
"reference clock id" => :reference, "system peer" => :peer,
|
|
14
|
+
"clock offset" => :offset_ms, "leap indicator" => :leap }.freeze
|
|
15
|
+
AUXILIARY = /\A(?:Local mode|NTP version|Clock jitter|Stability|Clock precision|Root delay|Root dispersion|Peer dispersion|Nominal frequency|Actual frequency|Reference time|System poll interval):/i
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# 解析 display ntp service status,保留不完整或不支持的证据。
|
|
20
|
+
def parse_output(text, complete:)
|
|
21
|
+
source = clean(text)
|
|
22
|
+
row, seen, local_sync = { component: "system" }, {}, []
|
|
23
|
+
source.each_line do |raw|
|
|
24
|
+
line = raw.strip
|
|
25
|
+
next if line.empty?
|
|
26
|
+
key, value = line.split(/:\s*/, 2)
|
|
27
|
+
field = FIELDS[key.downcase]
|
|
28
|
+
if field && value
|
|
29
|
+
if seen[field]
|
|
30
|
+
row[field] = nil
|
|
31
|
+
@issues << { code: :duplicate_ntp_field, field: field }
|
|
32
|
+
else
|
|
33
|
+
row[field] = parse_field(field, value)
|
|
34
|
+
end
|
|
35
|
+
seen[field] = true
|
|
36
|
+
elsif key.casecmp?("Sync state from NTP server to local clock")
|
|
37
|
+
local_sync << value&.downcase
|
|
38
|
+
elsif !line.empty? && !AUXILIARY.match?(line)
|
|
39
|
+
@issues << { code: :unrecognized_ntp_line }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
@issues << { code: :missing_ntp_status } unless seen[:synchronized]
|
|
43
|
+
apply_local_sync(row, local_sync) unless local_sync.empty?
|
|
44
|
+
if local_clock?(row[:peer]) || row[:peer].nil? && local_clock?(row[:reference])
|
|
45
|
+
row[:synchronized] = false
|
|
46
|
+
@issues << { code: :local_ntp_reference }
|
|
47
|
+
end
|
|
48
|
+
build_result(seen.any? ? [ row ] : [], complete: complete, recognized: seen.any?, identity: [ :component ])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 仅存在服务器关联不能证明设备时钟已受同步校准。
|
|
52
|
+
def apply_local_sync(row, states)
|
|
53
|
+
if states.any? { |state| [ "clock not synced", "clock unsynced" ].include?(state) }
|
|
54
|
+
row[:synchronized] = false
|
|
55
|
+
@issues << { code: :ntp_local_clock_unsynchronized }
|
|
56
|
+
elsif states != [ "clock synced" ]
|
|
57
|
+
row[:synchronized] = nil unless row[:synchronized] == false
|
|
58
|
+
@issues << { code: :unknown_ntp_local_clock_state }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 识别本地参考时钟,避免视为已同步的外部对等体。
|
|
63
|
+
def local_clock?(value)
|
|
64
|
+
/\A(?:LOCAL\(\d+\)|LOCL|127\.127\.1\.\d+)\z/i.match?(value.to_s)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 解释单个支持的命令字段,不丢弃其他无关事实。
|
|
68
|
+
def parse_field(field, value)
|
|
69
|
+
parsed = case field
|
|
70
|
+
when :synchronized then { "synchronized" => true, "unsynchronized" => false }[value.downcase]
|
|
71
|
+
when :stratum then value.to_i if /\A\d+\z/.match?(value) && (0..16).cover?(value.to_i)
|
|
72
|
+
when :leap then value.to_i(2) if /\A[01]{2}\z/.match?(value)
|
|
73
|
+
when :offset_ms
|
|
74
|
+
match = /\A(-?\d+(?:\.\d+)?)\s*ms\z/i.match(value)
|
|
75
|
+
measurement(match[1], field: field, component: "system") if match
|
|
76
|
+
when :reference, :peer then value unless /\A(?:none|--)\z/i.match?(value)
|
|
77
|
+
end
|
|
78
|
+
@issues << { code: :invalid_ntp_field, field: field } if parsed.nil? && !%i[reference peer].include?(field)
|
|
79
|
+
parsed
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 邻居表作用域由进程和区域标题确定。
|
|
10
|
+
class DisplayOspfPeer < CtxChecker::Parsers::Base
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# 解析 display ospf peer,保留不完整或不支持的证据。
|
|
14
|
+
def parse_output(text, complete:)
|
|
15
|
+
text = clean(text)
|
|
16
|
+
if /\AOSPF is not configured\.\z/i.match?(text.strip)
|
|
17
|
+
return build_result([], complete: complete, recognized: true, issues: [ { code: :protocol_not_configured } ])
|
|
18
|
+
end
|
|
19
|
+
rows, issues, process, area, header = [], [], nil, nil, false
|
|
20
|
+
text.each_line do |raw|
|
|
21
|
+
line = raw.strip
|
|
22
|
+
if (match = /\AOSPF Process (\d+) with Router ID ([\d.]+)\z/i.match(line))
|
|
23
|
+
process, area = match[1], nil
|
|
24
|
+
elsif (match = /\AArea:\s*([\d.]+)\z/i.match(line))
|
|
25
|
+
area = match[1]
|
|
26
|
+
elsif /\ARouter ID\s+Address\s+Pri\s+Dead-Time\s+State\s+Interface\z/i.match?(line)
|
|
27
|
+
header = true
|
|
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.valid_host?(match[1]) && CtxParser::Values.valid_host?(match[2])
|
|
30
|
+
issues << { code: :ospf_scope_missing } unless process && area
|
|
31
|
+
rows << { vrf: nil, process_id: process, area: area,
|
|
32
|
+
interface: CtxChecker::Parsers::InterfaceNames.normalize(match[5], platform: :h3c), router_id: match[1], neighbor_address: match[2],
|
|
33
|
+
state: match[3].downcase == "2-way" ? :two_way : match[3].downcase.to_sym,
|
|
34
|
+
network_type: :unknown, local_role: :unknown, neighbor_role: match[4] && match[4] != "-" ? match[4].downcase.to_sym : :unknown }
|
|
35
|
+
else
|
|
36
|
+
issues << { code: :invalid_ospf_peer }
|
|
37
|
+
end
|
|
38
|
+
elsif !line.empty? && line != "Neighbor Brief Information"
|
|
39
|
+
issues << { code: :unrecognized_ospf_line }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
build_result(rows, complete: complete, recognized: header && rows.any?, issues: issues, identity: %i[vrf process_id interface router_id])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "system_values"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 逐电源评估;电源缺失时仍需区分槽位是否应有模块。
|
|
10
|
+
class DisplayPower < CtxChecker::Parsers::Base
|
|
11
|
+
include SystemValues
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# 解析 display power,保留不完整或不支持的证据。
|
|
16
|
+
def parse_output(text, complete:)
|
|
17
|
+
source = clean(text)
|
|
18
|
+
rows, location, columns, contexts, context = [], "system", nil, [], nil
|
|
19
|
+
source.each_line do |raw|
|
|
20
|
+
line = raw.strip
|
|
21
|
+
if (match = /\A((?:Chassis \d+ )?Slot \d+|Chassis \d+)\s*:\z/i.match(line))
|
|
22
|
+
location, columns = scope_name(match[1]), nil
|
|
23
|
+
contexts << (context = { component: location, count: 0 })
|
|
24
|
+
elsif (match = /\APowerID\s+State\s+Mode\s+Current\(A\)\s+Voltage\(V\)\s+Power\(W\)(?:\s+(FanDirection))?(?:\s+(Switch))?\z/i.match(line))
|
|
25
|
+
columns = match.captures.compact.map(&:downcase)
|
|
26
|
+
elsif columns && (match = /\A(\d+)\s+(\S+)\s+(?:AC|DC|--)\s+#{NUMBER}\s+#{NUMBER}\s+(#{NUMBER})(?:\s+(\S+))?(?:\s+(\S+))?\z/i.match(line)) && match.captures.drop(3).compact.size == columns.size
|
|
27
|
+
row = power_record(location, match[1], match[2], match[3])
|
|
28
|
+
metadata = columns.zip(match.captures.drop(3).compact).to_h
|
|
29
|
+
if metadata.key?("switch") && !metadata["switch"].casecmp?("on")
|
|
30
|
+
row[:status] = :unknown if row[:status] == :ok
|
|
31
|
+
@issues << { code: :power_switch_not_on, component: row[:component] }
|
|
32
|
+
end
|
|
33
|
+
rows << row
|
|
34
|
+
context[:count] += 1 if context
|
|
35
|
+
elsif (match = /\APower\s+(\d+)\s+State:\s*(\S+)\z/i.match(line))
|
|
36
|
+
rows << power_record(location, match[1], match[2], nil)
|
|
37
|
+
context[:count] += 1 if context
|
|
38
|
+
elsif !line.empty? && !/\A(?:-+|Input Power:\s*(?:#{NUMBER}\s*(?:W|\(W\))|--|N\/?A|error))\z/i.match?(line)
|
|
39
|
+
@issues << { code: :unrecognized_power_line }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
contexts.each do |entry|
|
|
43
|
+
@issues << { code: :missing_power_scope_records, component: entry[:component] } if entry[:count].zero?
|
|
44
|
+
end
|
|
45
|
+
build_result(rows, complete: complete, recognized: rows.any?, identity: %i[component kind])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 构建包含状态与测量值的归一化电源记录。
|
|
49
|
+
def power_record(location, id, state, value)
|
|
50
|
+
component = "#{location}/power #{id}"
|
|
51
|
+
status = component_state(state, component: component)
|
|
52
|
+
watts = measurement(value, field: :watts, component: component)
|
|
53
|
+
if watts&.negative?
|
|
54
|
+
watts = nil
|
|
55
|
+
@issues << { code: :invalid_measurement, component: component, field: :watts }
|
|
56
|
+
end
|
|
57
|
+
{ component: component, kind: :power_supply, status: status,
|
|
58
|
+
present: status == :absent ? false : (status == :unknown ? nil : true), watts: watts }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../parsers/base"
|
|
4
|
+
require_relative "../../parsers/interface_names"
|
|
5
|
+
|
|
6
|
+
module CtxChecker
|
|
7
|
+
module H3c
|
|
8
|
+
module Parsers
|
|
9
|
+
# 诊断表保留各光通道及模块自身阈值。
|
|
10
|
+
class DisplayTransceiverDiagnosis < CtxChecker::Parsers::Base
|
|
11
|
+
NUMBER = /(?:-?\d+(?:\.\d+)?|N\/A|--)/i
|
|
12
|
+
ABSENT = /\A(?:The )?(?:transceiver is absent|no transceiver|transceiver is not present)\.?\z/i
|
|
13
|
+
DOM_UNSUPPORTED = /\A(?:(?:The )?transceiver does not support (?:digital diagnosis|diagnostic(?: information)?|this function)|(?:The )?diagnostic information is not supported)\.?\z/i
|
|
14
|
+
OPERATION_UNSUPPORTED = /\AThe operation is not supported\.\z/i
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# 解析 display transceiver diagnosis,保留不完整或不支持的证据。
|
|
19
|
+
def parse_output(text, complete:)
|
|
20
|
+
text = clean(text)
|
|
21
|
+
rows, issues = [], []
|
|
22
|
+
text.split(/(?=^\S+ transceiver diagnostic information:)/i).each do |block|
|
|
23
|
+
next if block.strip.empty?
|
|
24
|
+
match = /\A(\S+) transceiver diagnostic information:/i.match(block)
|
|
25
|
+
unless match
|
|
26
|
+
issues << { code: :unrecognized_optics_block }
|
|
27
|
+
next
|
|
28
|
+
end
|
|
29
|
+
name = CtxChecker::Parsers::InterfaceNames.normalize(match[1], platform: :h3c)
|
|
30
|
+
lines = block.lines.drop(1).map(&:strip).reject(&:empty?)
|
|
31
|
+
if (state = availability(lines, name))
|
|
32
|
+
rows.concat(measurements(name, nil, nil, nil, **state))
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
samples, limits, section, mode = [], {}, nil, nil
|
|
36
|
+
lines.each do |line|
|
|
37
|
+
case line
|
|
38
|
+
when /\ACurrent diagnostic parameters:/i then section, mode = :current, nil
|
|
39
|
+
when /\AAlarm thresholds:/i then section, mode = :alarm, nil
|
|
40
|
+
when /\AWarning thresholds:/i then section, mode = :warning, nil
|
|
41
|
+
when /RX power\(dBm\)\s+TX power\(dBm\)\z/i
|
|
42
|
+
mode = line.start_with?("[channel]") ? :lanes : :single
|
|
43
|
+
else
|
|
44
|
+
if section == :current && mode == :single && (sample = /\A#{NUMBER}\s+#{NUMBER}\s+#{NUMBER}\s+(#{NUMBER})\s+(#{NUMBER})\z/i.match(line))
|
|
45
|
+
samples << [ nil, number(sample[1], interface: name), number(sample[2], interface: name) ]
|
|
46
|
+
elsif section == :current && mode == :lanes && (sample = /\A(\d+)\s+#{NUMBER}\s+(#{NUMBER})\s+(#{NUMBER})\z/i.match(line))
|
|
47
|
+
samples << [ sample[1].to_i, number(sample[2], interface: name), number(sample[3], interface: name) ]
|
|
48
|
+
elsif %i[alarm warning].include?(section) && mode && (limit = /\A(High|Low)\s+#{NUMBER}\s+#{NUMBER}\s+#{NUMBER}\s+(#{NUMBER})\s+(#{NUMBER})\z/i.match(line))
|
|
49
|
+
key = "#{limit[1].downcase}_#{section}".to_sym
|
|
50
|
+
if limits.key?(key)
|
|
51
|
+
issues << { code: :duplicate_optics_threshold, interface: name }
|
|
52
|
+
else
|
|
53
|
+
limits[key] = [ number(limit[2], interface: name), number(limit[3], interface: name) ]
|
|
54
|
+
end
|
|
55
|
+
elsif line.match?(/\A(?:High|Low)\b/) || (mode && line.match?(/\A[-\d]/))
|
|
56
|
+
issues << { code: :invalid_optics_measurement, interface: name }
|
|
57
|
+
elsif !line.empty? && !line.match?(/\A-+\z/)
|
|
58
|
+
issues << { code: :unrecognized_optics_field, interface: name }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
issues << { code: :optics_measurements_missing, interface: name } if samples.empty?
|
|
63
|
+
samples.each do |lane, rx, tx|
|
|
64
|
+
measurements(name, lane, rx, tx, present: true, dom_supported: true).each_with_index do |record, index|
|
|
65
|
+
limits.each { |key, values| record[key] = values[index] }
|
|
66
|
+
rows << record
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
build_result(rows, complete: complete, recognized: rows.any?, issues: issues, identity: %i[interface lane metric])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 状态标记不能掩盖后续数据、重复状态或未知诊断内容。
|
|
74
|
+
def availability(lines, name)
|
|
75
|
+
absent = lines.any? { |line| ABSENT.match?(line) }
|
|
76
|
+
unsupported = lines.any? { |line| DOM_UNSUPPORTED.match?(line) }
|
|
77
|
+
operation_unsupported = lines.any? { |line| OPERATION_UNSUPPORTED.match?(line) }
|
|
78
|
+
return unless absent || unsupported || operation_unsupported
|
|
79
|
+
|
|
80
|
+
if lines.size != 1
|
|
81
|
+
@issues << { code: :ambiguous_optics_availability, interface: name }
|
|
82
|
+
return { present: nil, dom_supported: nil }
|
|
83
|
+
end
|
|
84
|
+
{ present: operation_unsupported ? nil : !absent, dom_supported: absent ? nil : false }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 解析数值测量,不将未知文本转换为零。
|
|
88
|
+
def number(value, interface:)
|
|
89
|
+
return unless value.match?(/\A-?\d+(?:\.\d+)?\z/)
|
|
90
|
+
|
|
91
|
+
number = Float(value)
|
|
92
|
+
return number if number.finite?
|
|
93
|
+
|
|
94
|
+
@issues << { code: :invalid_optics_measurement, interface: interface }
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 提取支持的传感器值,保留测量不可用的状态。
|
|
99
|
+
def measurements(name, lane, rx, tx, present:, dom_supported:)
|
|
100
|
+
[ [ :rx_power, rx ], [ :tx_power, tx ] ].map do |metric, value|
|
|
101
|
+
{ interface: name, lane: lane, metric: metric, value: value, unit: :dbm,
|
|
102
|
+
low_alarm: nil, low_warning: nil, high_warning: nil, high_alarm: nil, alarm: :unknown,
|
|
103
|
+
present: present, dom_supported: dom_supported }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CtxChecker
|
|
4
|
+
module H3c
|
|
5
|
+
module Parsers
|
|
6
|
+
# 共享 Comware 标量转换;测量缺失始终不能变成零。
|
|
7
|
+
module SystemValues
|
|
8
|
+
NUMBER = /(?:-?\d+(?:\.\d+)?|--|N\/?A|error)/i
|
|
9
|
+
COMPONENT_STATES = { "normal" => :ok, "ok" => :ok, "fault" => :failed,
|
|
10
|
+
"faulty" => :failed, "error" => :failed, "fandirectionfault" => :failed,
|
|
11
|
+
"absent" => :absent, "warning" => :warning }.freeze
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# 解析支持的传感器测量值及回显单位。
|
|
16
|
+
def measurement(value, field:, component:, percent: false)
|
|
17
|
+
return if value.nil? || /\A(?:--|N\/?A|error)\z/i.match?(value)
|
|
18
|
+
number = Float(value, exception: false)
|
|
19
|
+
return number if number&.finite? && (!percent || (0..100).cover?(number))
|
|
20
|
+
|
|
21
|
+
@issues << { code: :invalid_measurement, field: field, component: component }
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 解析非负计数,同时保留未知值。
|
|
26
|
+
def count(value, field:, component:)
|
|
27
|
+
return value.to_i if /\A\d+\z/.match?(value.to_s)
|
|
28
|
+
|
|
29
|
+
@issues << { code: :invalid_measurement, field: field, component: component }
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 按平台支持的标签归一化硬件组件状态。
|
|
34
|
+
def component_state(value, component:)
|
|
35
|
+
COMPONENT_STATES.fetch(value.downcase) do
|
|
36
|
+
@issues << { code: :unrecognized_component_state, component: component }
|
|
37
|
+
:unknown
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 确定模块或路由上下文的身份。
|
|
42
|
+
def scope_name(value) = value.downcase.strip.gsub(/\s+/, " ")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ctx_parser/profile"
|
|
4
|
+
require_relative "parsers/display_interface"
|
|
5
|
+
require_relative "parsers/display_link_aggregation_verbose"
|
|
6
|
+
require_relative "parsers/display_transceiver_diagnosis"
|
|
7
|
+
require_relative "parsers/display_bgp_peer"
|
|
8
|
+
require_relative "parsers/display_ospf_peer"
|
|
9
|
+
|
|
10
|
+
require_relative "parsers/display_cpu_usage"
|
|
11
|
+
require_relative "parsers/display_memory"
|
|
12
|
+
require_relative "parsers/display_environment"
|
|
13
|
+
require_relative "parsers/display_fan"
|
|
14
|
+
require_relative "parsers/display_power"
|
|
15
|
+
require_relative "parsers/display_clock"
|
|
16
|
+
require_relative "parsers/display_ntp_service_status"
|
|
17
|
+
require_relative "parsers/display_alarm_active"
|
|
18
|
+
require_relative "parsers/display_counters_inbound_interface"
|
|
19
|
+
require_relative "parsers/display_counters_outbound_interface"
|
|
20
|
+
|
|
21
|
+
module CtxChecker
|
|
22
|
+
module H3c
|
|
23
|
+
# 声明 Comware 只读查询;BGP 明确限定为公网 IPv4 单播。
|
|
24
|
+
class Profile < CtxParser::Profile
|
|
25
|
+
CATALOG = CtxParser::CommandCatalog.new([
|
|
26
|
+
{ dimensions: %i[interfaces counters], command: "display interface", parser: Parsers::DisplayInterface },
|
|
27
|
+
{ dimensions: [ :counters ], command: "display counters inbound interface", parser: Parsers::DisplayCountersInboundInterface },
|
|
28
|
+
{ dimensions: [ :counters ], command: "display counters outbound interface", parser: Parsers::DisplayCountersOutboundInterface },
|
|
29
|
+
{ dimensions: [ :aggregation ], command: "display link-aggregation verbose", parser: Parsers::DisplayLinkAggregationVerbose },
|
|
30
|
+
{ dimensions: [ :optics ], command: "display transceiver diagnosis interface", parser: Parsers::DisplayTransceiverDiagnosis },
|
|
31
|
+
{ dimensions: [ :bgp ], command: "display bgp peer ipv4", parser: Parsers::DisplayBgpPeer },
|
|
32
|
+
{ dimensions: [ :ospf ], command: "display ospf peer", parser: Parsers::DisplayOspfPeer },
|
|
33
|
+
{ dimensions: [ :cpu ], command: "display cpu-usage", parser: Parsers::DisplayCpuUsage },
|
|
34
|
+
{ dimensions: [ :memory ], command: "display memory", parser: Parsers::DisplayMemory },
|
|
35
|
+
{ dimensions: [ :temperature ], command: "display environment", parser: Parsers::DisplayEnvironment },
|
|
36
|
+
{ dimensions: [ :environment ], command: "display fan", parser: Parsers::DisplayFan },
|
|
37
|
+
{ dimensions: [ :power ], command: "display power", parser: Parsers::DisplayPower },
|
|
38
|
+
{ dimensions: [ :clock ], command: "display clock", parser: Parsers::DisplayClock },
|
|
39
|
+
{ dimensions: [ :ntp ], command: "display ntp-service status", parser: Parsers::DisplayNtpServiceStatus },
|
|
40
|
+
{ dimensions: [ :alarms ], command: "display alarm active", parser: Parsers::DisplayAlarmActive }
|
|
41
|
+
], multi_command: true)
|
|
42
|
+
COMMANDS = CATALOG.commands
|
|
43
|
+
PARSERS = CATALOG.parsers
|
|
44
|
+
|
|
45
|
+
# 每个已观察接口都必须保留入站与出站两个方向的证据状态。
|
|
46
|
+
def counter_metrics = %i[in_errors out_errors].freeze
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|