ctx_checker 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +23 -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 +13 -13
- data/lib/ctx_checker/version.rb +1 -1
- data/lib/ctx_checker.rb +1 -1
- metadata +11 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 271bedce96855d5603beaa8ad69dffa2bb7acf04a33057780f01db0f79dea270
|
|
4
|
+
data.tar.gz: cf2c7b5c9427f709c945541ebbb44dfb0cfdd070f729e40cf81e327444bd86d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 366fbedebe8f7e0eaba9f41a0d4b9057005818a29bf382bf8eecd8f3ea18aa1a37f001fdcbd96c8d00604831fdcd933de7cca5555dfdc8500668beea05323f00
|
|
7
|
+
data.tar.gz: 31ce9a2c85943188f3dd8ca2f26fe9770e9684925d68f319542d58116ea1f0f96c8e352dcfe185a2514d4e1cc020b3f028522f07523f45a3873bb63ef5058d9f
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Changes to the public contract are recorded here. Versions follow Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## 0.2.0 (2026-10-17)
|
|
6
|
+
|
|
7
|
+
- Require ctx_parser and ctx_driver 0.2.x for the renamed public contracts.
|
|
8
|
+
|
|
9
|
+
- Allow isolated package verification to install explicitly supplied dependency candidate artifacts without changing gemspec dependency constraints.
|
|
10
|
+
- Require a measured temperature when an exact sensor policy supplies numeric thresholds.
|
|
11
|
+
- Preserve conflicting Cisco interface states as incomplete evidence and retain observed protocol failures.
|
|
12
|
+
- Normalize integer OSPF process identities in policies, observations, and coverage scopes; reject normalized duplicates.
|
|
13
|
+
- Bound parser diagnostic samples to 128 plus an omitted-count summary, and skip vendor parsing for explicit command failures.
|
|
14
|
+
- Index validated command implementations for response parsing, rebuilding the index after discovery expansion.
|
|
15
|
+
|
|
16
|
+
- Use the shared `CtxParser::Configuration`, `Results`, and `Commands` names and dimension indexes where applicable.
|
|
17
|
+
|
|
18
|
+
- Name system field definitions and validation `SystemFields`; exercise installed BGP parsing during package verification.
|
|
19
|
+
|
|
20
|
+
- Rename domain APIs directly; callers must use the new names (no compatibility aliases).
|
|
21
|
+
|
|
22
|
+
- Name required-dimension predicates and policy validators explicitly; expose sampling interval units in counter evaluation.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Index CPU windows, optical measurements, aggregation membership, and required interfaces once, avoiding repeated full scans during parsing and health evaluation.
|
|
27
|
+
|
|
5
28
|
## 0.1.0 (2026-10-16)
|
|
6
29
|
|
|
7
30
|
### Added
|
data/README-zh.md
CHANGED
|
@@ -67,8 +67,16 @@ error、CRC、discard 保持独立,不相加;单次累计值不能当作当
|
|
|
67
67
|
|
|
68
68
|
生命周期 connector 是宿主适配器,提供 `open { |session| }`,VXLAN 和 Wireless 还要求 `platform`;原生 `Net::Connector::Base` 实例并不提供这一宿主生命周期协议。已连接的原生会话可传入 `CtxDriver::Reader` 或 `CtxChecker::Collector#collect_session`,不会隐式连接或重试。
|
|
69
69
|
|
|
70
|
+
## 领域术语
|
|
71
|
+
|
|
72
|
+
`Observation` 表示某维度的采集证据,`Finding` 表示健康判定。`Policy#required_dimension?`、`SystemPolicy#required_dimension?` 查询必需观测维度。接口与光学测量仍按其完整身份匹配要求。计数器阈值比较经过连续性验证的采样区间内增量,区间以秒计;阈值不是累计总量或每秒速率。
|
|
73
|
+
|
|
74
|
+
整数 OSPF 进程 ID 在策略、观测记录和覆盖作用域中统一转换成字符串,再检查身份重复。明确配置温度阈值时必须有数值读数,只有 `ok` 状态不能满足阈值策略。每个解析结果最多保留 128 条诊断样本,额外的 `diagnostics_truncated` 摘要记录省略数量;截断不能证明证据完整。
|
|
75
|
+
|
|
70
76
|
## 开发与测试
|
|
71
77
|
|
|
78
|
+
共享类按实际职责命名,平台信息放在命名空间中。直接对应设备命令的解析器保留文件名和类名。
|
|
79
|
+
|
|
72
80
|
```sh
|
|
73
81
|
bundle install
|
|
74
82
|
bundle exec rake
|
|
@@ -78,6 +86,8 @@ bundle exec rake build
|
|
|
78
86
|
|
|
79
87
|
`bundle exec rake` 执行 RSpec、RuboCop 及离线示例。测试放在 `spec/`,沿用项目 RSpec 约定;样例采用合成或脱敏证据。新增解析格式时补充成功、不完整、不支持及非法输入回归。离线测试不代表所有设备版本均通过实机验证。
|
|
80
88
|
|
|
89
|
+
运行 `ruby script/verify_package.rb /path/to/ctx_checker.gem`,验证使用已发布依赖的隔离安装。联动开发时可追加明确构建的依赖包路径,例如 `/path/to/ctx_parser.gem`,先安装这些候选包;候选包验证通过不代表注册表中的依赖已兼容。`bundle exec ruby -Ilib benchmark/runtime.rb` 用合成输入测量本地命令查找和诊断开销。
|
|
90
|
+
|
|
81
91
|
使用声明的最低 Ruby 版本检查兼容性。方法用途统一使用简明中文注释;复杂实现还需说明业务规则、实现思路及关键分支。保持公开返回值与错误契约。真实回显和凭据不可进入示例、fixture、日志或发布包。
|
|
82
92
|
|
|
83
93
|
## 发布
|
data/README.md
CHANGED
|
@@ -67,8 +67,16 @@ Detailed vendor behavior, historical validation notes, and Motor-only integratio
|
|
|
67
67
|
|
|
68
68
|
The lifecycle connector is a host adapter exposing `open { |session| }` (and `platform` for VXLAN and Wireless). It is distinct from a native `Net::Connector::Base` device instance. Borrowed native sessions are accepted by `CtxDriver::Reader` and `CtxChecker::Collector#collect_session`; they are never implicitly connected or retried.
|
|
69
69
|
|
|
70
|
+
## Domain vocabulary
|
|
71
|
+
|
|
72
|
+
An `Observation` describes collected evidence for a dimension; a `Finding` describes its health assessment. `Policy#required_dimension?` and `SystemPolicy#required_dimension?` query required observation dimensions. Interface and optical requirements retain their scoped identity checks. Counter limits compare increments over a validated sampling interval in seconds; they are not cumulative totals or rates.
|
|
73
|
+
|
|
74
|
+
Integer OSPF process IDs are normalized to strings in policies, observations, and covered scopes before checking identities. An explicit temperature threshold requires a numeric reading; an `ok` sensor state alone cannot satisfy it. Parsers retain at most 128 diagnostic samples per result, plus a `diagnostics_truncated` entry counting omitted diagnostics; truncation never proves complete evidence.
|
|
75
|
+
|
|
70
76
|
## Development and tests
|
|
71
77
|
|
|
78
|
+
Name shared classes by their responsibility and use the namespace for platform context. Preserve filenames and class names that directly match device commands.
|
|
79
|
+
|
|
72
80
|
```sh
|
|
73
81
|
bundle install
|
|
74
82
|
bundle exec rake
|
|
@@ -78,6 +86,8 @@ bundle exec rake build
|
|
|
78
86
|
|
|
79
87
|
`bundle exec rake` runs RSpec, RuboCop, and the offline examples. Tests live in `spec/`, following the existing project's RSpec convention; fixtures use synthetic or sanitized evidence. Add regression cases for successful, incomplete, unsupported, and malformed inputs when extending a parser. Offline tests do not certify all device versions or replace live integration checks.
|
|
80
88
|
|
|
89
|
+
Run `ruby script/verify_package.rb /path/to/ctx_checker.gem` to verify an isolated installation with published dependencies. During coordinated development, append explicitly built dependency artifacts, such as `/path/to/ctx_parser.gem`, to install those candidates first. Candidate verification does not establish that the published dependencies are compatible. `bundle exec ruby -Ilib benchmark/runtime.rb` measures synthetic command lookup and diagnostic overhead locally.
|
|
90
|
+
|
|
81
91
|
Use the declared minimum Ruby version for compatibility checks. Document source methods and non-obvious business logic in concise Chinese, preserve public return/error contracts, and never add raw device captures or credentials to examples, fixtures, logs, or release archives.
|
|
82
92
|
|
|
83
93
|
## Release
|
data/docs/architecture-zh.md
CHANGED
|
@@ -23,12 +23,12 @@ net-connector session -> Collector / CtxDriver::Reader -> 逐命令 Parser
|
|
|
23
23
|
### 文件组织
|
|
24
24
|
|
|
25
25
|
* `lib/ctx_checker/{observation,snapshot,policy,finding,report}.rb`:不可变领域对象和公共契约。
|
|
26
|
-
* `lib/ctx_checker/{
|
|
26
|
+
* `lib/ctx_checker/{system_fields,system_policy,observation_merger}.rb`:系统指标字段、运行阈值及多命令证据组合。
|
|
27
27
|
* `lib/ctx_checker/{evaluator,collector,runtime_parser,parser_registry}.rb`:判定组合、采集和离线入口。
|
|
28
28
|
* `lib/ctx_checker/checks/*.rb`:按指标组织的厂商无关检查。
|
|
29
29
|
* `lib/ctx_checker/parsers/{base,interface_names,scoped_result}.rb`:文本边界、完整性、接口名称及协议表范围证明。
|
|
30
30
|
* `lib/ctx_checker/{h3c,cisco_ios,cisco_nxos,palo_alto,hillstone,radware_alteon}/profile.rb`:只读查询目录及厂商发现策略。
|
|
31
|
-
* `lib/ctx_checker/<platform>/parsers/<command>.rb`:一个命令一个解析器;一份回显包含多个指标时返回
|
|
31
|
+
* `lib/ctx_checker/<platform>/parsers/<command>.rb`:一个命令一个解析器;一份回显包含多个指标时返回 Results。
|
|
32
32
|
|
|
33
33
|
不创建每厂商一个巨型解析模板。共享同一语法才共享解析机制;健康规则集中在 checks。
|
|
34
34
|
型号变体通过 Profile 策略或 MODEL_PARSERS 显式扩展,不按未知型号猜测命令。
|
|
@@ -15,11 +15,11 @@ module CtxChecker
|
|
|
15
15
|
def call(observation)
|
|
16
16
|
@observation = observation
|
|
17
17
|
if observation.status == :not_applicable
|
|
18
|
-
return [ finding(:fail, :required_feature_not_configured, severity: :critical) ] if @policy.
|
|
18
|
+
return [ finding(:fail, :required_feature_not_configured, severity: :critical) ] if @policy.required_dimension?(observation.name)
|
|
19
19
|
|
|
20
20
|
return [ finding(:not_applicable, :feature_not_configured) ]
|
|
21
21
|
end
|
|
22
|
-
if observation.status == :not_requested && !@policy.
|
|
22
|
+
if observation.status == :not_requested && !@policy.required_dimension?(observation.name)
|
|
23
23
|
return [ finding(:not_applicable, :not_requested) ]
|
|
24
24
|
end
|
|
25
25
|
results = observation.records.flat_map do |record|
|
|
@@ -51,8 +51,8 @@ module CtxChecker
|
|
|
51
51
|
def missing_status = @observation.complete? ? :fail : :unknown
|
|
52
52
|
# 仅在需要关联接口状态的检查中建立接口索引。
|
|
53
53
|
def interface(name)
|
|
54
|
-
@
|
|
55
|
-
@
|
|
54
|
+
@interfaces_by_name ||= @snapshot.observation(:interfaces).records.to_h { |row| [ row[:name], row ] }
|
|
55
|
+
@interfaces_by_name[name]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# 同级查询失败不能使其他来源的完整测量失效。
|
|
@@ -19,10 +19,10 @@ module CtxChecker
|
|
|
19
19
|
return results + [ finding(:unknown, :clock_time_unavailable, entity: entity) ]
|
|
20
20
|
end
|
|
21
21
|
observed_at = @observation.observed_at || @snapshot.observed_at
|
|
22
|
-
|
|
22
|
+
skew_seconds = (Time.iso8601(record[:time]) - observed_at).abs
|
|
23
23
|
limit = @policy.system.max_clock_skew_seconds
|
|
24
|
-
results << finding(
|
|
25
|
-
actual: { time: record[:time], skew_seconds:
|
|
24
|
+
results << finding(skew_seconds > limit ? :fail : :pass, :clock_skew, entity: entity,
|
|
25
|
+
actual: { time: record[:time], skew_seconds: skew_seconds }, expected: { maximum_seconds: limit }, severity: skew_seconds > limit ? :warning : :info)
|
|
26
26
|
results
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -10,7 +10,7 @@ module CtxChecker
|
|
|
10
10
|
def initialize(**options)
|
|
11
11
|
super
|
|
12
12
|
@previous_counters = @previous&.observation(dimension)
|
|
13
|
-
@
|
|
13
|
+
@previous_records_by_identity = @previous_counters&.records&.to_h { |row| [ row.values_at(*identity_fields), row ] } || {}
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
private
|
|
@@ -21,7 +21,7 @@ module CtxChecker
|
|
|
21
21
|
value = record[:value]
|
|
22
22
|
return finding(:unknown, :counter_unavailable, entity: entity) if value.nil?
|
|
23
23
|
previous = @previous_counters
|
|
24
|
-
prior = @
|
|
24
|
+
prior = @previous_records_by_identity[record.values_at(*identity_fields)]
|
|
25
25
|
if (reason = packet_semantics_reason(record, prior))
|
|
26
26
|
return finding(:unknown, reason, entity: entity, actual: record.slice(:value, :unit, :scope))
|
|
27
27
|
end
|
|
@@ -30,9 +30,9 @@ module CtxChecker
|
|
|
30
30
|
return finding(value.zero? ? :pass : :unknown, value.zero? ? :zero_cumulative_counter : :cumulative_errors,
|
|
31
31
|
entity: entity, actual: value, severity: value.zero? ? :info : :warning)
|
|
32
32
|
end
|
|
33
|
-
|
|
33
|
+
interval_seconds = sample_time(record, @observation, @snapshot) - sample_time(prior, previous, @previous)
|
|
34
34
|
# 先验证时间与重置标记;数值下降可能是清零或重启,不能按负增量计算。
|
|
35
|
-
reason = if
|
|
35
|
+
reason = if interval_seconds <= 0
|
|
36
36
|
:counter_sample_time_invalid
|
|
37
37
|
elsif record[:discontinuity].nil? || prior[:discontinuity].nil?
|
|
38
38
|
:counter_continuity_unconfirmed
|
|
@@ -45,10 +45,10 @@ module CtxChecker
|
|
|
45
45
|
limit = limit_for(record)
|
|
46
46
|
# 超限是已观察故障;未超限仍需前后来源完整才能证明通过。
|
|
47
47
|
if delta <= limit && (!record_complete?(prior, previous) || !record_complete?(record, @observation))
|
|
48
|
-
return finding(:unknown, :counter_evidence_incomplete, entity: entity, actual: { delta: delta, seconds:
|
|
48
|
+
return finding(:unknown, :counter_evidence_incomplete, entity: entity, actual: { delta: delta, seconds: interval_seconds })
|
|
49
49
|
end
|
|
50
50
|
finding(delta > limit ? :fail : :pass, :counter_delta, entity: entity,
|
|
51
|
-
actual: delta_measurement(value, delta,
|
|
51
|
+
actual: delta_measurement(value, delta, interval_seconds), expected: { maximum_delta: limit }, severity: :warning)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# 选择当前评估负责的观测维度。
|
|
@@ -58,7 +58,7 @@ module CtxChecker
|
|
|
58
58
|
# 优先使用资源专用阈值,再回退到策略默认值。
|
|
59
59
|
def limit_for(record) = @policy.counter_limits.fetch(record[:metric], 0)
|
|
60
60
|
# 将已计算增量与采样间隔组织为报告测量值。
|
|
61
|
-
def delta_measurement(_value, delta,
|
|
61
|
+
def delta_measurement(_value, delta, interval_seconds) = { delta: delta, seconds: interval_seconds }
|
|
62
62
|
# 解析速率或增量分析所需的采样时间。
|
|
63
63
|
def sample_time(record, observation, snapshot) = record[:sampled_at] ? Time.iso8601(record[:sampled_at]) : observation.observed_at || snapshot.observed_at
|
|
64
64
|
|
|
@@ -26,7 +26,7 @@ module CtxChecker
|
|
|
26
26
|
# 优先使用资源专用阈值,再回退到策略默认值。
|
|
27
27
|
def limit_for(record) = @policy.system.device_counter_limits.fetch(record[:metric], 0)
|
|
28
28
|
# 组织累计量、已验证增量及采样间隔,并计算每秒速率。
|
|
29
|
-
def delta_measurement(value, delta,
|
|
29
|
+
def delta_measurement(value, delta, interval_seconds) = { cumulative: value, delta: delta, seconds: interval_seconds, per_second: delta / interval_seconds }
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -11,7 +11,7 @@ module CtxChecker
|
|
|
11
11
|
# 按策略评估资源,同时保留测量值缺失的状态。
|
|
12
12
|
def assess(record)
|
|
13
13
|
name = record[:name]
|
|
14
|
-
required = @policy.
|
|
14
|
+
required = @policy.required_interface?(name)
|
|
15
15
|
status, reason = if record[:admin_status] == :down && !required
|
|
16
16
|
[ :not_applicable, :administratively_down ]
|
|
17
17
|
elsif record[:oper_status] == :down || record[:protocol_status] == :down && record[:protocol_applicable] != false || required && record[:admin_status] == :down
|
|
@@ -20,7 +20,7 @@ module CtxChecker
|
|
|
20
20
|
end
|
|
21
21
|
context = interface(record[:interface])
|
|
22
22
|
if context && record_complete?(context, @snapshot.observation(:interfaces)) &&
|
|
23
|
-
context[:admin_status] == :down && !@policy.
|
|
23
|
+
context[:admin_status] == :down && !@policy.required_interface?(record[:interface]) && !@policy.required_optics?(record)
|
|
24
24
|
return finding(:not_applicable, :administratively_down, entity: entity)
|
|
25
25
|
end
|
|
26
26
|
if (record[:present] == false || record[:dom_supported] == false) && !ALARMS.include?(record[:alarm])
|
|
@@ -33,6 +33,8 @@ module CtxChecker
|
|
|
33
33
|
if !override && (state == :ok && severity || explicit_fault && !severity)
|
|
34
34
|
results << finding(:unknown, :temperature_state_disagreement, entity: entity)
|
|
35
35
|
end
|
|
36
|
+
elsif override && value.nil?
|
|
37
|
+
results << finding(:unknown, :temperature_value_unavailable, entity: entity, expected: bounds)
|
|
36
38
|
elsif !explicit_fault
|
|
37
39
|
results << finding(state == :ok ? :pass : :unknown, :temperature_state, entity: entity, actual: record)
|
|
38
40
|
end
|
|
@@ -14,7 +14,7 @@ module CtxChecker
|
|
|
14
14
|
|
|
15
15
|
# 解析 show clock detail,保留不完整或不支持的证据。
|
|
16
16
|
def parse_output(text, complete:)
|
|
17
|
-
records, issues = [],
|
|
17
|
+
records, issues = [], diagnostics
|
|
18
18
|
time_source = nil
|
|
19
19
|
clean(text).each_line.with_index(1) do |source, number|
|
|
20
20
|
line = source.strip
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
4
|
require_relative "system_values"
|
|
5
|
-
require "ctx_parser/
|
|
5
|
+
require "ctx_parser/results"
|
|
6
6
|
|
|
7
7
|
module CtxChecker
|
|
8
8
|
module CiscoIos
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
# 解析 show environment all,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
@rows = { temperature: [], environment: [], power: [] }
|
|
19
|
-
@problems = @rows.transform_values {
|
|
19
|
+
@problems = @rows.transform_values { diagnostics }
|
|
20
20
|
@switch = "active"
|
|
21
21
|
@temperature = nil
|
|
22
22
|
@sensor_table = false
|
|
@@ -33,7 +33,7 @@ module CtxChecker
|
|
|
33
33
|
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
34
34
|
[ dimension, build_result(rows, complete: complete, recognized: rows.any?, issues: @problems[dimension], identity: identity) ]
|
|
35
35
|
end
|
|
36
|
-
CtxParser::
|
|
36
|
+
CtxParser::Results.new(by_dimension: results)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Catalyst StackWise Virtual 在传感器后按交换机报告电源和风扇托盘。
|
|
@@ -18,7 +18,7 @@ module CtxChecker
|
|
|
18
18
|
# 要求存在数据表或明确空表证据;标志图例不能作为成员记录。
|
|
19
19
|
def parse_output(text, complete:)
|
|
20
20
|
text = clean(text)
|
|
21
|
-
rows, issues, groups, totals = [],
|
|
21
|
+
rows, issues, groups, totals = [], diagnostics, [], []
|
|
22
22
|
current, header, empty = nil, false, false
|
|
23
23
|
text.each_line.with_index(1) do |source, number|
|
|
24
24
|
line = source.strip
|
|
@@ -49,16 +49,19 @@ module CtxChecker
|
|
|
49
49
|
totals.each do |total|
|
|
50
50
|
issues << { code: :count_mismatch, expected: total, observed: groups.size } unless total == groups.size
|
|
51
51
|
end
|
|
52
|
+
member_groups = rows.to_h { |row| [ row[:group], true ] }
|
|
52
53
|
groups.each do |group|
|
|
53
|
-
next if
|
|
54
|
+
next if member_groups.key?(group[:group])
|
|
54
55
|
|
|
55
56
|
rows << group.merge(member: nil, member_status: :unknown)
|
|
57
|
+
member_groups[group[:group]] = true
|
|
56
58
|
issues << { code: :aggregation_members_missing, group: group[:group] }
|
|
57
59
|
end
|
|
58
60
|
repeated_groups = groups.group_by { |group| group[:group] }.select { |_, entries| entries.size > 1 }.keys
|
|
59
61
|
if repeated_groups.any?
|
|
60
62
|
issues << { code: :duplicate_aggregation_group, count: repeated_groups.size }
|
|
61
|
-
|
|
63
|
+
repeated = repeated_groups.to_h { |name| [ name, true ] }
|
|
64
|
+
rows.reject! { |row| repeated.key?(row[:group]) }
|
|
62
65
|
end
|
|
63
66
|
issues << { code: :contradictory_empty } if empty && groups.any?
|
|
64
67
|
build_result(rows, complete: complete, recognized: empty || (header && groups.any?), issues: issues, identity: %i[group member])
|
|
@@ -11,7 +11,7 @@ module CtxChecker
|
|
|
11
11
|
|
|
12
12
|
# 解析 show facility alarm status,保留不完整或不支持的证据。
|
|
13
13
|
def parse_output(text, complete:)
|
|
14
|
-
records, issues, totals, severities = [],
|
|
14
|
+
records, issues, totals, severities = [], diagnostics, [], []
|
|
15
15
|
clean(text).each_line.with_index(1) do |source, number|
|
|
16
16
|
line = source.strip
|
|
17
17
|
next if line.empty? || /\A[- ]+\z/.match?(line) || /\ASource\s+(?:Time\s+)?Severity\s+/.match?(line)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../parsers/base"
|
|
4
4
|
require_relative "../../parsers/interface_names"
|
|
5
|
-
require "ctx_parser/
|
|
5
|
+
require "ctx_parser/results"
|
|
6
6
|
|
|
7
7
|
module CtxChecker
|
|
8
8
|
module CiscoIos
|
|
@@ -23,22 +23,24 @@ module CtxChecker
|
|
|
23
23
|
# 分别跟踪覆盖率;有效状态头不能证明计数器已采集。
|
|
24
24
|
def parse_output(text, complete:)
|
|
25
25
|
text = clean(text)
|
|
26
|
-
interfaces, counters, state_issues, counter_issues = [], [],
|
|
26
|
+
interfaces, counters, state_issues, counter_issues = [], [], diagnostics, diagnostics
|
|
27
27
|
current = nil
|
|
28
|
+
states = {}
|
|
28
29
|
text.each_line.with_index(1) do |source, number|
|
|
29
30
|
line = source.strip
|
|
30
31
|
next if line.empty?
|
|
31
32
|
|
|
32
33
|
if (match = interface_header(line))
|
|
33
34
|
current = interface_record(match)
|
|
35
|
+
states = current.slice(:admin_status, :protocol_status).transform_values { |value| value == :unknown ? [] : [ value ] }
|
|
34
36
|
interfaces << current
|
|
35
37
|
elsif (match = /\A(\S+) is /.match(line)) && CtxChecker::Parsers::InterfaceNames.kind(normalize_name(match[1])) != :unknown
|
|
36
38
|
current = nil
|
|
37
39
|
state_issues << { code: :unparsed_interface_header, line: number }
|
|
38
40
|
elsif current && (match = /\Aadmin state is (up|down)\b/i.match(line))
|
|
39
|
-
current
|
|
41
|
+
assign_state(current, :admin_status, match[1].downcase.to_sym, states, state_issues, number)
|
|
40
42
|
elsif current && (match = /\ALine protocol is (up|down)\b/i.match(line))
|
|
41
|
-
current
|
|
43
|
+
assign_state(current, :protocol_status, match[1].downcase.to_sym, states, state_issues, number)
|
|
42
44
|
elsif current && (match = /\ALast clearing of "show interfaces?" counters\s+(.+)\z/i.match(line))
|
|
43
45
|
# 相对清零时长不能证明两个采样间的计数器连续性。
|
|
44
46
|
current[:discontinuity] = nil
|
|
@@ -84,13 +86,25 @@ module CtxChecker
|
|
|
84
86
|
interfaces.select { |record| record[:kind] == :physical }.each do |record|
|
|
85
87
|
counters.concat(complete_counter_coverage(record, counters_by_interface.fetch(record[:name], []), counter_issues))
|
|
86
88
|
end
|
|
87
|
-
CtxParser::
|
|
89
|
+
CtxParser::Results.new(by_dimension: {
|
|
88
90
|
interfaces: build_result(interfaces, complete: complete, recognized: interfaces.any?, issues: state_issues, identity: [ :name ]),
|
|
89
91
|
counters: build_result(counters, complete: complete, recognized: counters.any?, issues: state_issues + counter_issues,
|
|
90
92
|
identity: %i[interface metric])
|
|
91
93
|
})
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
# 冲突协议状态保留 down;冲突管理状态不能据此豁免接口检查。
|
|
97
|
+
def assign_state(record, field, value, states, issues, number)
|
|
98
|
+
values = states.fetch(field)
|
|
99
|
+
values << value unless values.include?(value)
|
|
100
|
+
record[field] = if values.size > 1
|
|
101
|
+
issues << { code: :conflicting_interface_state, field: field, interface: record[:name], line: number }
|
|
102
|
+
field == :protocol_status ? :down : :unknown
|
|
103
|
+
else
|
|
104
|
+
value
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
94
108
|
# 按计数器标签匹配,不误取总包数,并保留格式错误的指标行。
|
|
95
109
|
def counter_line?(line)
|
|
96
110
|
line.match?(/\b(?:input errors?|output errors?|CRC(?:\/FCS)?|input discards?|output discards?|Total output drops)\b/i) ||
|
|
@@ -16,7 +16,7 @@ module CtxChecker
|
|
|
16
16
|
# 解析 show interfaces transceiver detail,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
18
|
text = clean(text)
|
|
19
|
-
rows, issues, ports = [],
|
|
19
|
+
rows, issues, ports = [], diagnostics, []
|
|
20
20
|
metric, header, lane_column = nil, false, false
|
|
21
21
|
text.each_line.with_index(1) do |source, number|
|
|
22
22
|
line = source.strip
|
|
@@ -48,11 +48,12 @@ module CtxChecker
|
|
|
48
48
|
issues << { code: :unparsed_row, line: number }
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
|
+
measurements = rows.to_h { |row| [ row.values_at(:interface, :lane, :metric), true ] }
|
|
51
52
|
identities = rows.map { |row| row.values_at(:interface, :lane) }
|
|
52
53
|
identities.concat((ports.uniq - rows.map { |row| row[:interface] }).map { |name| [ name, nil ] })
|
|
53
54
|
identities.uniq.each do |name, lane|
|
|
54
55
|
%i[tx_power rx_power].each do |expected|
|
|
55
|
-
next if
|
|
56
|
+
next if measurements.key?([ name, lane, expected ])
|
|
56
57
|
|
|
57
58
|
issues << { code: :optical_measurement_missing, interface: name, lane: lane, metric: expected }
|
|
58
59
|
end
|
|
@@ -19,7 +19,7 @@ module CtxChecker
|
|
|
19
19
|
# 使用作用域与行身份,避免不同地址族或 VRF 相互覆盖。
|
|
20
20
|
def parse_output(text, complete:)
|
|
21
21
|
text = clean(text).gsub(/State\/\s*\n\s*PfxRcd/, "State/PfxRcd")
|
|
22
|
-
rows, issues = [],
|
|
22
|
+
rows, issues = [], diagnostics
|
|
23
23
|
scope = { vrf: "default", afi_safi: self.class::ADDRESS_FAMILY }
|
|
24
24
|
sections = []
|
|
25
25
|
section = { header: false, empty: false, count: 0, scope_fields: [], scope: scope }
|
|
@@ -46,7 +46,7 @@ module CtxChecker
|
|
|
46
46
|
section[:empty] = true
|
|
47
47
|
elsif parse_statistics(line, section, scope, issues, number)
|
|
48
48
|
next
|
|
49
|
-
elsif CtxParser::Values.
|
|
49
|
+
elsif CtxParser::Values.ip_address?(line)
|
|
50
50
|
issues << { code: :unparsed_row, line: number - 1 } if pending
|
|
51
51
|
pending = line
|
|
52
52
|
elsif peer_row?(line) || (pending && line.match?(/\A4\s/))
|
|
@@ -77,7 +77,7 @@ module CtxChecker
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# 识别协议对等体记录,排除摘要和表头。
|
|
80
|
-
def peer_row?(line) = CtxParser::Values.
|
|
80
|
+
def peer_row?(line) = CtxParser::Values.ip_address?(line.split.first.to_s)
|
|
81
81
|
|
|
82
82
|
# 解析单个路由作用域,不合并无关的 VRF 证据。
|
|
83
83
|
def parse_scope(line, scope, issues, number)
|
|
@@ -124,7 +124,7 @@ module CtxChecker
|
|
|
124
124
|
# 归一化单个路由对等体及其协议状态。
|
|
125
125
|
def parse_peer(line, scope)
|
|
126
126
|
match = /\A(\S+)\s+4\s+(\d+(?:\.\d+)?)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\S+\s+(.+)\z/.match(line)
|
|
127
|
-
return unless match && CtxParser::Values.
|
|
127
|
+
return unless match && CtxParser::Values.ip_address?(match[1])
|
|
128
128
|
|
|
129
129
|
remote_as = as_number(match[2])
|
|
130
130
|
return unless remote_as
|
|
@@ -132,7 +132,7 @@ module CtxChecker
|
|
|
132
132
|
state, prefixes = peer_state(match[3])
|
|
133
133
|
return unless state
|
|
134
134
|
|
|
135
|
-
scope.merge(peer: CtxParser::Values.
|
|
135
|
+
scope.merge(peer: CtxParser::Values.ip_address(match[1]), state: state, received_prefixes: prefixes, remote_as: remote_as)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
# 仅在不存在平台附加标记时,末列数字才能证明会话已建立。
|
|
@@ -19,7 +19,7 @@ module CtxChecker
|
|
|
19
19
|
# 解析 show ip ospf neighbor,保留不完整或不支持的证据。
|
|
20
20
|
def parse_output(text, complete:)
|
|
21
21
|
text = clean(text)
|
|
22
|
-
rows, issues = [],
|
|
22
|
+
rows, issues = [], diagnostics
|
|
23
23
|
scope = { vrf: "default", process_id: nil, area: nil }
|
|
24
24
|
sections = []
|
|
25
25
|
section = { header: false, empty: false, count: 0, totals: [], declared: false, scope: scope }
|
|
@@ -81,14 +81,14 @@ module CtxChecker
|
|
|
81
81
|
ipv4_address?(match[1]) && ipv4_address?(match[5])
|
|
82
82
|
|
|
83
83
|
scope.merge(interface: CtxChecker::Parsers::InterfaceNames.normalize(match[6], platform: self.class::PLATFORM),
|
|
84
|
-
router_id: CtxParser::Values.
|
|
84
|
+
router_id: CtxParser::Values.ip_address(match[1]), neighbor_address: CtxParser::Values.ip_address(match[5]),
|
|
85
85
|
state: STATES.fetch(match[3].upcase), network_type: :unknown, local_role: :unknown,
|
|
86
86
|
neighbor_role: match[4] == "-" ? :unknown : match[4].downcase.to_sym)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
# OSPFv2 路由器 ID 与接口地址必须是 IPv4 主机地址。
|
|
90
90
|
def ipv4_address?(text)
|
|
91
|
-
text.match?(/\A\d+(?:\.\d+){3}\z/) && CtxParser::Values.
|
|
91
|
+
text.match?(/\A\d+(?:\.\d+){3}\z/) && CtxParser::Values.ip_address?(text)
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
@@ -11,7 +11,7 @@ module CtxChecker
|
|
|
11
11
|
|
|
12
12
|
# 解析 show memory statistics,保留不完整或不支持的证据。
|
|
13
13
|
def parse_output(text, complete:)
|
|
14
|
-
records, issues = [],
|
|
14
|
+
records, issues = [], diagnostics
|
|
15
15
|
clean(text).each_line.with_index(1) do |source, number|
|
|
16
16
|
line = source.strip
|
|
17
17
|
next if line.empty? || /\AHead\s+Total\(b\)/.match?(line) || /\ATracekey\s*:\s*[\da-f#]+\z/i.match?(line)
|
|
@@ -15,7 +15,7 @@ module CtxChecker
|
|
|
15
15
|
|
|
16
16
|
# 解析 show ntp status,保留不完整或不支持的证据。
|
|
17
17
|
def parse_output(text, complete:)
|
|
18
|
-
records, issues = [],
|
|
18
|
+
records, issues = [], diagnostics
|
|
19
19
|
clean(text).each_line.with_index(1) do |source, number|
|
|
20
20
|
line = source.strip
|
|
21
21
|
next if line.empty?
|
|
@@ -14,7 +14,7 @@ module CtxChecker
|
|
|
14
14
|
|
|
15
15
|
# 解析 show processes cpu,保留不完整或不支持的证据。
|
|
16
16
|
def parse_output(text, complete:)
|
|
17
|
-
records, issues = [],
|
|
17
|
+
records, issues = [], diagnostics
|
|
18
18
|
recognized = false
|
|
19
19
|
clean(text).each_line.with_index(1) do |source, number|
|
|
20
20
|
line = source.strip
|
|
@@ -18,7 +18,7 @@ module CtxChecker
|
|
|
18
18
|
module CiscoIos
|
|
19
19
|
# IOS 与 IOS-XE 共用命令契约;型号特有语法通过解析器覆盖。
|
|
20
20
|
class Profile < CtxParser::Profile
|
|
21
|
-
|
|
21
|
+
DECLARATIONS = CtxParser::Commands.new([
|
|
22
22
|
{ dimensions: %i[interfaces counters], command: "show interfaces", parser: Parsers::ShowInterfaces },
|
|
23
23
|
{ dimensions: [ :aggregation ], command: "show etherchannel summary", parser: Parsers::ShowEtherchannelSummary },
|
|
24
24
|
{ dimensions: [ :optics ], command: "show interfaces transceiver detail", parser: Parsers::ShowInterfacesTransceiverDetail },
|
|
@@ -32,8 +32,8 @@ module CtxChecker
|
|
|
32
32
|
{ dimensions: [ :alarms ], command: "show facility-alarm status", parser: Parsers::ShowFacilityAlarmStatus },
|
|
33
33
|
{ dimensions: [ :device_counters ], command: "show ip traffic", parser: Parsers::ShowIpTraffic }
|
|
34
34
|
])
|
|
35
|
-
COMMANDS =
|
|
36
|
-
PARSERS =
|
|
35
|
+
COMMANDS = DECLARATIONS.by_dimension
|
|
36
|
+
PARSERS = DECLARATIONS.parsers
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
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
|
|
@@ -18,7 +18,7 @@ module CtxChecker
|
|
|
18
18
|
# 解析 show environment,保留不完整或不支持的证据。
|
|
19
19
|
def parse_output(text, complete:)
|
|
20
20
|
@rows = { temperature: [], environment: [], power: [] }
|
|
21
|
-
@problems = @rows.transform_values {
|
|
21
|
+
@problems = @rows.transform_values { diagnostics }
|
|
22
22
|
@section = nil
|
|
23
23
|
@section_size = 0
|
|
24
24
|
@power_columns = nil
|
|
@@ -34,7 +34,7 @@ module CtxChecker
|
|
|
34
34
|
(dimension ? [ @problems[dimension] ] : @problems.values).each { |issues| issues << unparsed(number) }
|
|
35
35
|
end
|
|
36
36
|
finish_section
|
|
37
|
-
CtxParser::
|
|
37
|
+
CtxParser::Results.new(by_dimension: @rows.to_h do |dimension, rows|
|
|
38
38
|
identity = dimension == :temperature ? [ :component ] : %i[component kind]
|
|
39
39
|
[ dimension, build_result(rows, complete: complete, recognized: rows.any?, issues: @problems[dimension], identity: identity) ]
|
|
40
40
|
end)
|