vxlan 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README-zh.md +2 -2
- data/README.md +7 -3
- data/bin/README.md +2 -2
- data/docs/evidence-contracts.md +10 -0
- data/docs/superpowers/plans/2026-10-23-apply-cli-boundaries.md +45 -0
- data/docs/superpowers/plans/2026-10-23-evpn-summary-evidence.md +45 -0
- data/docs/superpowers/specs/2026-10-23-apply-cli-boundaries.md +13 -0
- data/docs/superpowers/specs/2026-10-23-evpn-summary-evidence.md +31 -0
- data/lib/vxlan/change_plan/svi_acl.rb +7 -7
- data/lib/vxlan/change_plan.rb +7 -7
- data/lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb +70 -20
- data/lib/vxlan/cli/apply.rb +35 -34
- data/lib/vxlan/cli/collect.rb +4 -4
- data/lib/vxlan/cli/options.rb +42 -41
- data/lib/vxlan/cli/parse.rb +12 -12
- data/lib/vxlan/cli/report.rb +11 -11
- data/lib/vxlan/cli/review/fabric.rb +21 -21
- data/lib/vxlan/cli/review/report.rb +28 -28
- data/lib/vxlan/cli/review.rb +14 -14
- data/lib/vxlan/cli.rb +2 -2
- data/lib/vxlan/fabric/review.rb +42 -42
- data/lib/vxlan/fabric/scope.rb +6 -6
- data/lib/vxlan/recommendations.rb +26 -26
- data/lib/vxlan/version.rb +1 -1
- metadata +19 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cd74f1778008071770a76681129f64d7fc8d803ca27336fbac450147e33cd01
|
|
4
|
+
data.tar.gz: d4ba8a592e430b4e8c2a81e2218fe65f70c617d5fce7f60744d893a734a80b53
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e45e95ed6294d0f1ae0014d60ef13b1f8fad1f60a5832815b46966e1159d35b63159700410afe0f1191cb05155806b0ee5ad73050f27019e76646dfc462a5d63
|
|
7
|
+
data.tar.gz: 84d292f0dc18f67f949a73cd545293556d5e24722d995a9c161eb5a2889a89bc5cbf4afce379da9660c8bf803741a74b6b5e1ec63d47e9adac2d6214bfc65cfc
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ Changes to the public contract are recorded here. Versions follow Semantic Versi
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.5.1 (2026-10-24)
|
|
8
|
+
|
|
9
|
+
- Validate EVPN configured-peer totals within each VRF and address-family block, retain administrative shutdown and unknown AS identities, and preserve state-valued prefix totals.
|
|
10
|
+
- Use shared CLI argument and diagnostic boundaries for apply; preserve exit status, cancellation and uncertain journals when stderr fails, without replaying commands.
|
|
11
|
+
- Consume JSON Pointer configuration provenance and leave shared gem dependency versions to host lockfiles.
|
|
12
|
+
|
|
13
|
+
## 0.5.0 (2026-10-23)
|
|
14
|
+
|
|
15
|
+
- Consume immutable string-keyed Runner summary documents throughout reports and progress callbacks.
|
|
16
|
+
- Use English diagnostics, CLI help, change plans and Fabric review reports.
|
|
17
|
+
- Require Runner 0.2, net-connector 0.18 and the coordinated command receipt protocol.
|
|
18
|
+
|
|
7
19
|
## 0.4.2 (2026-10-23)
|
|
8
20
|
|
|
9
21
|
- Migrate shared CLI entrypoints to ctx_cli 0.2.0 names without legacy aliases.
|
data/README-zh.md
CHANGED
|
@@ -21,7 +21,7 @@ gem "vxlan"
|
|
|
21
21
|
|
|
22
22
|
然后执行 `bundle install`;也可直接执行 `gem install vxlan`。Ruby 加载入口为 `vxlan`,公开命名空间为 `Vxlan`;gem 包名与 Ruby 命名空间分别维护。
|
|
23
23
|
|
|
24
|
-
模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver)
|
|
24
|
+
模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`ctx_runner`](https://github.com/gatework/ctx_runner), [`ctx_cli`](https://github.com/gatework/ctx_cli)。gemspec 不限制依赖版本;应用应提交经过验证的 `Gemfile.lock`,保持部署可复现。使用本库不需要启动 Rails 或配置兄弟模块的本地路径。
|
|
25
25
|
|
|
26
26
|
发布产物和校验值见 [GitHub Releases](https://github.com/gatework/vxlan/releases),已发布版本以 [RubyGems](https://rubygems.org/gems/vxlan) 为准。
|
|
27
27
|
|
|
@@ -59,7 +59,7 @@ observation.records.each { |record| p record }
|
|
|
59
59
|
```sh
|
|
60
60
|
gem install vxlan
|
|
61
61
|
# 联网采集与读取 .env 所需的可选依赖:
|
|
62
|
-
gem install net-connector -v '~> 0.
|
|
62
|
+
gem install net-connector -v '~> 0.18.0'
|
|
63
63
|
vxlan --help
|
|
64
64
|
vxlan collect --init --non-interactive
|
|
65
65
|
vxlan collect --inventory-only
|
data/README.md
CHANGED
|
@@ -21,7 +21,7 @@ gem "vxlan"
|
|
|
21
21
|
|
|
22
22
|
Then run `bundle install`, or install directly with `gem install vxlan`. The require path is `vxlan` and the public namespace is `Vxlan`; package names and Ruby namespaces are intentionally separate.
|
|
23
23
|
|
|
24
|
-
Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver).
|
|
24
|
+
Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`ctx_runner`](https://github.com/gatework/ctx_runner), [`ctx_cli`](https://github.com/gatework/ctx_cli). The gemspec leaves their versions unconstrained; applications should commit a verified `Gemfile.lock` for reproducible deployments. No Rails boot or sibling checkout paths are required.
|
|
25
25
|
|
|
26
26
|
Release artifacts and checksums are available from [GitHub Releases](https://github.com/gatework/vxlan/releases). Check [RubyGems](https://rubygems.org/gems/vxlan) for published versions.
|
|
27
27
|
|
|
@@ -59,7 +59,7 @@ The gem installs `vxlan`; its implementation lives in `lib/vxlan/cli/`. The scri
|
|
|
59
59
|
```sh
|
|
60
60
|
gem install vxlan
|
|
61
61
|
# Optional dependencies for live collection and .env files:
|
|
62
|
-
gem install net-connector -v '~> 0.
|
|
62
|
+
gem install net-connector -v '~> 0.18.0'
|
|
63
63
|
vxlan --help
|
|
64
64
|
vxlan collect --init --non-interactive
|
|
65
65
|
vxlan collect --inventory-only
|
|
@@ -90,7 +90,9 @@ For native sessions, the host holds `native.with_operation(:vxlan)` around the f
|
|
|
90
90
|
|
|
91
91
|
## Domain vocabulary
|
|
92
92
|
|
|
93
|
-
Runtime variants preserve unavailable NVE router MACs as `nil`, optional empty CDP `dc_uuid` as `nil`, and starred vPC `down*` members as `state: "down"` with `forwarding_via_peer_link: true`. Wrapped vPC consistency/reason fields are joined only for the recognized continuation. BGP prefix statistics follow supported route-type columns in the header, with or without `PfxRcd`, and remain bounded by the main neighbor's received-prefix count. A blank command body or an empty L2RIB table without explicit empty/count evidence remains partial.
|
|
93
|
+
Runtime variants preserve unavailable NVE router MACs as `nil`, optional empty CDP `dc_uuid` as `nil`, and starred vPC `down*` members as `state: "down"` with `forwarding_via_peer_link: true`. Wrapped vPC consistency/reason fields are joined only for the recognized continuation. BGP prefix statistics follow supported route-type columns in the header, with or without `PfxRcd`, and remain bounded by the main neighbor's received-prefix count when that total is known. A blank command body or an empty L2RIB table without explicit empty/count evidence remains partial.
|
|
94
|
+
|
|
95
|
+
EVPN configured-peer totals are checked within each VRF/address-family block; capable peers are distinct from the complete neighbor list. Administrative shutdown is retained as `shut_(admin)`. Unestablished AS 0 neighbors expose `remote_as: nil`; a state-valued prefix column keeps `prefixes_received: nil`, even when route-type counters are shown. Published-format sources and verification limits are recorded in [the evidence contracts](docs/evidence-contracts.md#evpn-summary-evidence-update-2026-10-23).
|
|
94
96
|
|
|
95
97
|
VLAN IDs, VNIs, VRFs, and NVE members are separate identities. Configuration review joins VLAN mappings and NVE members by VNI; it does not infer operational reachability. `BgpNeighbors` enumerates configured neighbors after peer-template inheritance, not established BGP sessions. `PrefixListReferences` describes route-map selectors and their routing consumers; those references do not by themselves prove route acceptance.
|
|
96
98
|
|
|
@@ -137,4 +139,6 @@ MIT. See [LICENSE](LICENSE).
|
|
|
137
139
|
|
|
138
140
|
Review output follows the net-connector topology layout: `summary.txt`, `summary.json`, `devices/*.txt`, optional `scripts/*.cfg`, and a checksummed `plan.json`. Generated reports contain no Markdown. Select exactly one change with `vxlan apply --plan plan.json --item ID`; interactive confirmation remains mandatory.
|
|
139
141
|
|
|
142
|
+
Apply returns `2` on failure and `130` on interruption even if stderr cannot accept diagnostics. Existing execution journals retain uncertain device effects; commands are never replayed automatically. Unexpected positional arguments are rejected, including after `--help`.
|
|
143
|
+
|
|
140
144
|
Offline review requires `snapshot.target.host` to match the canonical device host. Reports without target identity must be collected again; the unbound legacy format is no longer accepted.
|
data/bin/README.md
CHANGED
|
@@ -17,7 +17,7 @@ bundle exec ruby examples/offline.rb
|
|
|
17
17
|
|
|
18
18
|
`bin/` 的公开内容是 `vxlan` 与本说明;现场 `vxlan.yml`、`.env`、`vxlans/` 由用户按需创建,不进入版本库或安装包。配置和凭据模板统一位于 `lib/vxlan/cli/templates/`,使用 `--init` 生成,不维护重复模板。
|
|
19
19
|
|
|
20
|
-
离线解析和帮助不加载 net-connector。采集配置与联网入口可选依赖 `net-connector ~> 0.
|
|
20
|
+
离线解析和帮助不加载 net-connector。采集配置与联网入口可选依赖 `net-connector ~> 0.18.0`(已包含 `.env` 加载依赖 dotenv)。本仓库 Gemfile 的 `examples` 组已声明连接器,不进入 vxlan.gemspec 的运行依赖。只安装 vxlan gem 的宿主需要自行声明相应可选依赖。
|
|
21
21
|
|
|
22
22
|
## 正式命令
|
|
23
23
|
|
|
@@ -25,7 +25,7 @@ bundle exec ruby examples/offline.rb
|
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
27
|
gem install vxlan
|
|
28
|
-
gem install net-connector -v '~> 0.
|
|
28
|
+
gem install net-connector -v '~> 0.18.0'
|
|
29
29
|
vxlan --help
|
|
30
30
|
vxlan collect --init --non-interactive
|
|
31
31
|
vxlan collect --inventory-only
|
data/docs/evidence-contracts.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# 证据契约与验收记录
|
|
2
2
|
|
|
3
|
+
## EVPN summary evidence update (2026-10-23)
|
|
4
|
+
|
|
5
|
+
配置邻居总数在每个物理 VRF/AF 段内核对所有主表记录;缺失、额外记录和矛盾声明使证据为 partial。capable 与全部记录数不同,仅核对其不超过配置总数及重复声明一致性,不据它推断逐邻居健康。零配置邻居可证明空表;没有声明总数的历史格式仍按已有语法/完整采集契约处理。
|
|
6
|
+
|
|
7
|
+
管理关闭状态保留为 `shut_(admin)`;未建立会话显示的 AS 0 表达为 `remote_as: nil`,已建立行仍必须有合法非零 AS。路由类型表允许 PfxRcd 显示与主表一致的状态,保留 `prefixes_received: nil`,不以类型计数合计伪造总数。已知总数继续约束类型计数之和;缺失、重复、身份或状态不匹配的统计仍为 partial。后续重复主表头不会清除之前未完成的统计证据。
|
|
8
|
+
|
|
9
|
+
依据为 [Cisco NX-OS 9.3(x) 配置指南](https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/label-switching/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x_chapter_01000.html) 中的管理关闭/soft reconfiguration 格式、[Cisco DevNet BGP CLI 参考](https://developer.cisco.com/docs/nx-api-cli-reference-for-the-cisco-nexus-9000-series-platform/bgp-commands/) 的 AS 0/Idle 与 dampening 格式,以及 [Cisco Multisite 排障文档](https://www.cisco.com/c/en/us/support/docs/switches/nexus-9000-series-switches/215780-troubleshoot-evpn-vxlan-in-multisite-env.html) 的主表与类型统计关联。最后一份材料明确列出对应边界网关为 N9K-C93108TC-FX、NX-OS 9.3(1)。测试使用保留地址重组的文档格式,不是新的实机采集,也不证明所有设备版本兼容。
|
|
10
|
+
|
|
11
|
+
现有解析测试验收上述边界。契约变异脚本在临时副本中检验本仓库实现,并复制当前 Bundler 选中的 Runner 库以验证已迁移的报告/执行契约;不改动已安装 gem,不隐式加载相邻源码。运行结果记录依赖来源和版本。历史证据与既有源样本继续保留如下。
|
|
12
|
+
|
|
3
13
|
本轮从代码规范审查收紧到三条纵向链路:解析事实与完整性、失败与报告收尾、声明规则一致性。方案见 [实施计划](superpowers/plans/2026-10-19-evidence-contracts.md),约束见 [设计](superpowers/specs/2026-10-19-evidence-contracts.md)。已有 WIP 保留,未提交或发布。
|
|
4
14
|
|
|
5
15
|
## 来源与预期
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Apply CLI boundaries implementation plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Reuse existing parameter/output capabilities and preserve apply failure/cancellation outcomes when stderr fails.
|
|
6
|
+
|
|
7
|
+
**Architecture:** CtxCli::Command parses, Terminal confirms, Output delivers safe diagnostics. VXLAN/Wireless select and validate plans, acquire credentials and own session leases. CtxRunner::Execution owns effects and journals. No new abstraction or state machine is introduced.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby 4.0.6, RSpec, existing project Bundler/rake and gem verification scripts.
|
|
10
|
+
|
|
11
|
+
**Spec:** docs/superpowers/specs/2026-10-23-apply-cli-boundaries.md
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- Preserve WIP and concurrent dependency edits; use two isolated worktrees with guarded copy-back. No commit, push, release, constraint restoration or evidence deletion.
|
|
16
|
+
- Chinese method comments and English eight-module implementation literals; preserve Wireless domain language and vendor layouts.
|
|
17
|
+
- Keep port 22, execution-directory defaults, all flags and full confirmation tokens. Do not add --yes or implicit plan selection.
|
|
18
|
+
- Ten scoped files: each consumer's apply implementation, apply spec, CHANGELOG and README; this plan/spec live in VXLAN. Current selected dependency bytes and metadata must be identified in package evidence.
|
|
19
|
+
|
|
20
|
+
## Review Focus
|
|
21
|
+
|
|
22
|
+
- Broken stderr before credentials/connection returns 2 and does not consume confirmation or send commands.
|
|
23
|
+
- Broken stderr after one uncertain command or interruption preserves status 2/130 and its journal without replay.
|
|
24
|
+
- Failed journal finalization cannot mask the original result or disable later failure diagnostics.
|
|
25
|
+
- Help with positional arguments is rejected; ordinary help avoids TTY, file reads, credentials and connections.
|
|
26
|
+
- A reused apply instance starts a fresh parse/execution state, and the refactor leaves exact domain selection, checksum, token and lease behavior intact.
|
|
27
|
+
|
|
28
|
+
### Task 1: migrate both apply consumers to existing CLI boundaries
|
|
29
|
+
|
|
30
|
+
**Files:** vxlan/lib/vxlan/cli/apply.rb, wireless/lib/wireless/cli/apply.rb; both spec/cli/apply_spec.rb, CHANGELOG.md and README.md; VXLAN plan/spec.
|
|
31
|
+
|
|
32
|
+
**Interfaces:** Consume existing CtxCli::Command.new/parse/informational? and Output.new/diagnostic. Produce unchanged Apply#run(argv) statuses 0/1/2/130 and unchanged Execution/session contracts. No shared-library implementation change.
|
|
33
|
+
|
|
34
|
+
- [x] Extend existing exercise helpers only for broken diagnostic streams and interrupted command writes. Add consumer regressions for preflight failure (including failed intent persistence), one-command error, and interruption; assert statuses, no extra writes and actual journal state/error. Add --help plus unexpected positional argument rejection, checking no input consumption or connection.
|
|
35
|
+
- [x] Run each `bundle exec rspec spec/cli/apply_spec.rb`. Expected: new diagnostic scenarios fail by escaping IOError; help/extra returns 0 instead of 2. Existing domain cases pass.
|
|
36
|
+
- [x] Replace direct OptionParser lifecycle with CtxCli::Command and use informational? before TTY/plan reads. Keep host-defined argument registrations and domain workflow. Route final errors and journal warnings through a per-entrypoint CtxCli::Output; preserve the existing Interrupt guard for the optional journal warning.
|
|
37
|
+
- [x] Run focused apply specs and both full `bundle exec rake` gates, plus existing ctx_cli default gate. Expected: all pass; domain checksum/selection/no-replay/readback tests retain their behavior. Add concise changelog/README notes.
|
|
38
|
+
- [x] Run one fresh independent review of the exact phase diff, spec and ledger. Re-grade findings, handle Critical/Important once with RED/GREEN and full affected gates; no second review.
|
|
39
|
+
- [x] Copy only hash-guarded scoped files to actual checkouts. Run both actual default rake gates and ordinary bin apply --help; build both exact candidate packages and run official package verifiers with explicit net-connector/Expect/Runner/CLI artifacts. Add an installed broken-stderr/interruption probe. Expected: actual and installed behavior passes, source bytes match and unrelated WIP remains.
|
|
40
|
+
|
|
41
|
+
## Self-review
|
|
42
|
+
|
|
43
|
+
All behavior requirements map to Task 1; existing fixtures already cover domain fingerprints, selection, persistence, callback count and readback. The shared Command tests cover fresh defaults and argv ownership; consumer help tests cover the integration boundary. The minimal direct error-rescue patch would leave duplicated diagnostic handling and parser behavior; existing components supply both without any new API. Current continuous implementation authorization settles native execution and reversible integration; no further approval is needed.
|
|
44
|
+
|
|
45
|
+
Verification follow-through: the Wireless package verifier needed one fixture field (`snapshot.target.host`) to satisfy the existing evidence identity contract. This scoped eleventh file was repaired after the independent ten-file review and verified by the official package gate and lint. See the phase ledger for the original failure and rerun results.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# EVPN summary evidence implementation plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan inline, with one fresh final reviewer.
|
|
4
|
+
|
|
5
|
+
**Goal:** Interpret documented EVPN neighbor and statistics evidence without silently accepting missing declared neighbors or dropping known down states.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep command grammar and peer/statistics association in the current platform parser. Separate neighbor normalization, statistics-row association and declaration validation into small private operations where each expresses a distinct invariant. Reuse existing scope splitting, duplicate identity protection and count mismatch diagnostics.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby 4.0.6, RSpec, project Bundler/rake, existing contract and package verification tools.
|
|
10
|
+
|
|
11
|
+
**Spec:** docs/superpowers/specs/2026-10-23-evpn-summary-evidence.md
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- Nine VXLAN scoped files plus one Wireless test dependency repair: current parser, existing parser spec, README, CHANGELOG, docs/evidence-contracts.md, script/verify_contracts.rb, spec/cli/apply_spec.rb and this plan/spec; Wireless only adds its direct apply require in the existing apply spec. No new shared API, dependencies, version or vendor layout changes.
|
|
16
|
+
- Worktree starts with 149 byte-matched actual files, including WIP. No commit/push/release, constraint restoration or removal of evidence/worktrees.
|
|
17
|
+
- Preserve output completeness versus neighbor health, unknown AS and unknown prefix total explicitly. Do not infer service expectations or issue device changes.
|
|
18
|
+
- Source references document exact observed formats; supported field relationships are stated in the spec, without all-version/device claims.
|
|
19
|
+
|
|
20
|
+
## Review Focus
|
|
21
|
+
|
|
22
|
+
- Counts stay within physical scope, while conflicting router and duplicate peer identities still invalidate the logical scope.
|
|
23
|
+
- Capable is never treated as the total row count; repeated contradictory counters cannot be erased by a later original value.
|
|
24
|
+
- AS 0 cannot become a valid established AS; nil can join only a matching unknown-AS statistics row.
|
|
25
|
+
- State-valued PfxRcd, multiword administrative states, optional totals and absent/duplicate/invalid type columns cannot silently change state or associate statistics to the wrong peer.
|
|
26
|
+
- Malformed decorations, invalid integers, zero-prefix established rows and explicit empty tables preserve their distinct evidence meanings.
|
|
27
|
+
|
|
28
|
+
### Task 1: enforce documented summary evidence
|
|
29
|
+
|
|
30
|
+
**Files:** lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb; spec/cisco_nxos/show_bgp_l2vpn_evpn_summary_spec.rb; README.md; CHANGELOG.md; docs/evidence-contracts.md; script/verify_contracts.rb; spec/cli/apply_spec.rb; this plan/spec. Wireless spec/cli/apply_spec.rb only corrects its own direct require.
|
|
31
|
+
|
|
32
|
+
**Interfaces:** Consume unchanged `.parse(text, complete:)`; produce unchanged Result shape with documented state strings, optional `remote_as: nil` only for newly accepted unestablished AS 0 rows, and stable existing numeric AS semantics. Keep prefixes_received nil for down states.
|
|
33
|
+
|
|
34
|
+
- [x] Add focused behavior cases for configured counts, zero/absent declarations, contradictory counts and separate scopes; administrative state and AS boundaries; known decorations; state-valued statistics and mismatched/duplicate/missing statistics. Reuse existing test helpers and fixtures.
|
|
35
|
+
- [x] Run `bundle exec rspec spec/cisco_nxos/show_bgp_l2vpn_evpn_summary_spec.rb spec/runtime_parser_spec.rb`; verify new cases fail for the intended evidence defects while the original 40 cases still pass.
|
|
36
|
+
- [x] Implement the private parsing boundaries described above; do not loosen unknown grammar or introduce fallback APIs. Run focused specs to GREEN.
|
|
37
|
+
- [x] Add source-backed contract/README/changelog notes. Update contract mutation locations to the current parser guard and isolated selected Runner implementation, preserving all existing mutation cases and their non-assertion control. Run `bundle exec rake` and `bundle exec ruby script/verify_contracts.rb`; record selected dependencies and source paths.
|
|
38
|
+
- [x] Run one fresh independent review of the exact phase diff, spec, ledger and official source evidence. Re-grade findings by behavior; perform one Critical/Important fix pass with RED/GREEN and full affected gates, no re-review.
|
|
39
|
+
- [x] Guard and copy scoped files to the actual checkout; run actual default rake, contract checks and ordinary parse entrypoint. Build and verify the exact candidate, including installed grammar probes and unchanged earlier apply behavior. Compare final source/metadata/package bytes and original WIP hashes.
|
|
40
|
+
|
|
41
|
+
## Self-review
|
|
42
|
+
|
|
43
|
+
The fix follows evidence fields rather than collapsing parsing into a generic parser. Configured-peer totals describe the unfiltered command returned by the profile; a caller declaring filtered output complete can only receive partial evidence when counts disagree. Capable/established equality is deliberately not inferred. Existing terminal/identity/prefix upper-bound contracts remain in force. Continuous implementation authorization covers this local correction and guarded copy-back; no new approval gate is needed.
|
|
44
|
+
|
|
45
|
+
Review status: completed on 2026-10-24 by a fresh independent reviewer after two earlier starts failed with workspace credit exhaustion. No Critical/Important/Minor findings; 89 independent direct-source checks plus complete:false passed. Actual source, full gates and exact installed candidates were already verified; final acceptance packaging includes this updated plan. See .ctx-evpn-audit-20261023/review-findings.md and .ctx-evpn-acceptance-20261024 for evidence boundaries.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Apply CLI parameter and diagnostic boundaries
|
|
2
|
+
|
|
3
|
+
Continue the authorized eight-module review; Wireless participates as the existing shared-CLI consumer. The accepted shared CLI design says apply reuses parameter/terminal/output components while domain plan selection, validation, commands and operation leases remain local. Do not introduce a common apply workflow, move Execution accounting, or add adapters that duplicate the two domains' decisions.
|
|
4
|
+
|
|
5
|
+
Both current apply entrypoints directly build OptionParser and write final errors to stderr. A broken error stream can replace intended status 2 or 130 after the journal already recorded failure/interruption. Help also accepts unexpected positional arguments, unlike the existing CtxCli::Command boundary.
|
|
6
|
+
|
|
7
|
+
Use existing CtxCli::Command for fresh defaults, parsing and informational output, and CtxCli::Output for safe terminal diagnostics. Preserve every existing flag/default, exact confirmation token, per-domain preview text, checksum/selection rule, credentials policy, execution journal and session lease. Reject positional arguments including after --help before confirmation, file reads or connection. Informational help remains side-effect free. Runtime error messages remain safe and do not expose original exception text.
|
|
8
|
+
|
|
9
|
+
Broken stderr must not replace ordinary failure status 2 or interruption status 130, cause device replay, or erase execution facts. Report finalization warnings use the same diagnostic output component. Preserve the current special handling of interruption during the optional journal warning; do not introduce a new fatal-exception policy into shared Output.
|
|
10
|
+
|
|
11
|
+
Chinese method comments and English business literals apply to the eight requested modules. Wireless's existing domain language is preserved; this phase does not perform a bulk translation. No new public API, flags, dependencies, compatibility paths, version-constraint edits, commits or publication. Keep concurrent WIP using before/after hashes.
|
|
12
|
+
|
|
13
|
+
Acceptance uses existing domain apply fixtures and real journal files, then complete module gates, actual ordinary CLI entrypoints and isolated packages with explicit dependency artifacts. Selected installed versions are evidence only for that combination, not the concurrently widened dependency ranges. Real network/device behavior is unclaimed.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# EVPN summary evidence boundaries
|
|
2
|
+
|
|
3
|
+
## Problem and scope
|
|
4
|
+
|
|
5
|
+
The current NX-OS EVPN summary parser ignores declared neighbor counts, drops documented administrative shutdown and AS 0/Idle rows, and rejects a documented Idle row in the optional route-type statistics table. These are evidence interpretation defects: collection completeness and neighbor health must remain separate.
|
|
6
|
+
|
|
7
|
+
Only the existing `ShowBgpL2vpnEvpnSummary` implementation and its direct verification/docs change. Keep the vendor command filename/class, Result/Observation interfaces, profile commands and caller-owned completeness contract. No generic BGP framework, legacy API, connection changes or inferred configuration are needed.
|
|
8
|
+
|
|
9
|
+
## Source evidence
|
|
10
|
+
|
|
11
|
+
- [NX-OS 9.3(x) configuration guide](https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/label-switching/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x_chapter_01000.html) contains four configured neighbors, one capable neighbor, three administrative shutdown rows and inbound soft-reconfiguration statistics.
|
|
12
|
+
- [Cisco DevNet NX-API CLI reference](https://developer.cisco.com/docs/nx-api-cli-reference-for-the-cisco-nexus-9000-series-platform/bgp-commands/) shows an EVPN Idle neighbor with AS 0 and a dampening summary. AS 0 is retained as unknown (`remote_as: nil`) for a non-established row; it is never accepted as an established AS.
|
|
13
|
+
- [Cisco multisite troubleshooting](https://www.cisco.com/c/en/us/support/docs/switches/nexus-9000-series-switches/215780-troubleshoot-evpn-vxlan-in-multisite-env.html), updated 2022-10-17, includes three main-table neighbors and three route-type rows, with Idle in both tables. Its component list identifies that border gateway as N9K-C93108TC-FX, NX-OS 9.3(1).
|
|
14
|
+
|
|
15
|
+
These are published-format evidence, not local real-device verification. Tests use independently assembled documentation-style input with reserved addresses and controlled counters; do not copy a document's complete transcript or represent synthetic fixtures as live captures.
|
|
16
|
+
|
|
17
|
+
## Required behavior
|
|
18
|
+
|
|
19
|
+
1. Count every parsed main-table neighbor against every configured-peer declaration in its physical VRF/AF block. Keep complete blocks separate. A missing/excess row or contradictory declaration yields partial evidence while retaining unambiguous rows. A valid zero declaration proves an empty EVPN table. Count-free historical format remains supported.
|
|
20
|
+
2. `capable` is a different count: do not compare it to all neighbor rows or infer a peer's health from it. Reject impossible capable > configured and contradictory repeated count pairs. Do not assume capable equals the number of currently established rows in all versions.
|
|
21
|
+
3. Keep `Shut (Admin)` as normalized `shut_(admin)`, with no received-prefix total. Preserve existing normalization of other states. Non-established AS 0 becomes nil; nonzero valid AS remains numeric. Reject AS 0 for established rows and AS values outside the 32-bit range.
|
|
22
|
+
4. Recognize only the documented, fully shaped soft-reconfiguration/dampening summary lines. Unknown or malformed decorations still make the output partial.
|
|
23
|
+
5. The optional route-type table joins by normalized VRF/AF/address and matching AS. If it has a PfxRcd column, numeric totals must match established rows; a state must match the non-established row's state, including multiword administrative states. Type counters remain nonnegative integers. With a known total, their sum cannot exceed it. With an unknown total, retain the shown counters without inventing a zero total or applying an unsupported upper bound.
|
|
24
|
+
6. Missing/repeated/mismatched/invalid route-type rows remain partial. Every main-table row must be accounted for if that statistics table is present. Prefix observations must not turn a down neighbor into an established neighbor. No statistics table remains a valid format.
|
|
25
|
+
7. Repeated physical blocks still pass the existing logical-scope identity conflict and duplicate-neighbor checks. `complete: false` always remains partial. Invalid scope, malformed rows and terminal corruption retain existing rejection.
|
|
26
|
+
|
|
27
|
+
## Delivery
|
|
28
|
+
|
|
29
|
+
RED/GREEN in the current WIP snapshot worktree, full default rake and evidence-contract verifier, one independent final review, guarded copy-back, actual checkout gates/ordinary parse entrypoint, package byte comparison and isolated installed parsing. Preserve all unrelated WIP and concurrent dependency metadata, with exact selected versions recorded. No commit, push or release.
|
|
30
|
+
|
|
31
|
+
Chinese implementation comments and English literals continue to apply. Tests and documentation are not subject to bulk language translation.
|
|
@@ -12,14 +12,14 @@ module Vxlan
|
|
|
12
12
|
def validate_parameters!
|
|
13
13
|
super
|
|
14
14
|
name, direction = @change.values_at("acl", "direction")
|
|
15
|
-
raise ArgumentError, "ACL
|
|
16
|
-
raise ArgumentError, "ACL
|
|
17
|
-
raise ArgumentError, "
|
|
15
|
+
raise ArgumentError, "Invalid ACL parameters" unless name.is_a?(String) && name.match?(/\A[A-Za-z0-9_.-]{1,63}\z/) && %w[in out].include?(direction)
|
|
16
|
+
raise ArgumentError, "Invalid ACL creation state" unless [ true, false ].include?(@change["create_acl"])
|
|
17
|
+
raise ArgumentError, "Cannot overwrite an existing binding" unless [ nil, name ].include?(@change["previous_binding"])
|
|
18
18
|
groups = @change.fetch("address_groups", {})
|
|
19
|
-
raise ArgumentError, "ACL
|
|
19
|
+
raise ArgumentError, "ACL dependencies are unverified" unless groups.is_a?(Hash)
|
|
20
20
|
@policy = AclPolicy.resolve({ "address_groups" => groups }, @change["entries"])
|
|
21
|
-
raise ArgumentError, "ACL
|
|
22
|
-
raise ArgumentError, "
|
|
21
|
+
raise ArgumentError, "ACL rules or dependencies are unverified" unless @policy && @policy.address_groups == groups
|
|
22
|
+
raise ArgumentError, "No valid ACL rules" if @policy.rules.empty?
|
|
23
23
|
@rule_commands = rule_commands
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ module Vxlan
|
|
|
29
29
|
next if entry["action"] == "remark"
|
|
30
30
|
action, expression = entry.values_at("action", "expression")
|
|
31
31
|
unless %w[permit deny].include?(action) && expression.is_a?(String) && expression.match?(/\A(?:ip|tcp|udp|icmp|igmp|gre|esp|ah|ospf) [A-Za-z0-9_.:\/ -]+\z/)
|
|
32
|
-
raise ArgumentError, "ACL
|
|
32
|
+
raise ArgumentError, "ACL rule syntax is unverified"
|
|
33
33
|
end
|
|
34
34
|
" #{entry['sequence']} #{action} #{expression}"
|
|
35
35
|
end
|
data/lib/vxlan/change_plan.rb
CHANGED
|
@@ -15,12 +15,12 @@ module Vxlan
|
|
|
15
15
|
|
|
16
16
|
# 类型判别只发生在输入边界,不接受任意脚本或未声明字段。
|
|
17
17
|
def self.build(change)
|
|
18
|
-
raise ArgumentError, "
|
|
18
|
+
raise ArgumentError, "Change must be a mapping" unless change.is_a?(Hash)
|
|
19
19
|
case change["kind"]
|
|
20
20
|
when "vlan_vni_add" then VlanVni.new(change)
|
|
21
21
|
when "svi_gateway_add" then SviGateway.new(change)
|
|
22
22
|
when "svi_acl" then SviAcl.new(change)
|
|
23
|
-
else raise ArgumentError, "
|
|
23
|
+
else raise ArgumentError, "Unsupported change type"
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -34,9 +34,9 @@ module Vxlan
|
|
|
34
34
|
|
|
35
35
|
# 构造时一次校验和渲染,随后调用者不能改变已确认的参数或命令。
|
|
36
36
|
def initialize(change)
|
|
37
|
-
raise ArgumentError, "
|
|
37
|
+
raise ArgumentError, "Use ChangePlan.build to construct a domain change" if instance_of?(ChangePlan)
|
|
38
38
|
unless change.is_a?(Hash) && change.keys.all?(String) && (change.keys - self.class::FIELDS).empty? && change["kind"] == self.class::KIND
|
|
39
|
-
raise ArgumentError, "
|
|
39
|
+
raise ArgumentError, "Invalid change fields"
|
|
40
40
|
end
|
|
41
41
|
@change = CtxParser::Immutable.copy(change)
|
|
42
42
|
validate_parameters!
|
|
@@ -66,8 +66,8 @@ module Vxlan
|
|
|
66
66
|
|
|
67
67
|
# 三种动作均定位已有 L2 业务,VLAN/VNI 不从字符串或名称猜测。
|
|
68
68
|
def validate_parameters!
|
|
69
|
-
raise ArgumentError, "VLAN
|
|
70
|
-
raise ArgumentError, "VNI
|
|
69
|
+
raise ArgumentError, "Invalid VLAN" unless @change["vlan"].is_a?(Integer) && @change["vlan"].between?(1, 4094)
|
|
70
|
+
raise ArgumentError, "Invalid VNI" unless @change["vni"].is_a?(Integer) && @change["vni"].between?(1, 16_777_215)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# 网关相关增量必须携带明确租户及单个已验证 IPv4 前缀。
|
|
@@ -75,7 +75,7 @@ module Vxlan
|
|
|
75
75
|
address = @change["address"]
|
|
76
76
|
unless @change["vrf"].is_a?(String) && !@change["vrf"].empty? && address.is_a?(String) &&
|
|
77
77
|
address.match?(/\A\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}\z/) && IPAddr.new(address).ipv4?
|
|
78
|
-
raise ArgumentError, "
|
|
78
|
+
raise ArgumentError, "Verified service gateway is missing"
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -17,6 +17,9 @@ module Vxlan
|
|
|
17
17
|
FIELDS = %i[peer_ip version remote_as messages_received messages_sent table_version input_queue output_queue uptime state].freeze
|
|
18
18
|
INTEGERS = %i[version remote_as messages_received messages_sent table_version input_queue output_queue].freeze
|
|
19
19
|
PREFIX_TYPES = %i[type1 type2 type3 type4 type5 type6 type7 type8 type12].freeze
|
|
20
|
+
TABLE = /\ABGP table version is \d+(?:, L2VPN EVPN config peers (\d+), capable peers (\d+))?\z/i
|
|
21
|
+
STATE = /\A(?:\d+|Idle(?: \(Admin\))?|Active|Connect|OpenSent|OpenConfirm|Shutdown|Shut \(Admin\))\z/i
|
|
22
|
+
DETAILS = /\A(?:\d+ network entries and \d+ paths using \d+ bytes of memory|BGP attribute entries \[\d+\/\d+\], BGP AS path entries \[\d+\/\d+\]|BGP community entries \[\d+\/\d+\], BGP clusterlist entries \[\d+\/\d+\]|\d+ received paths for inbound soft reconfiguration|\d+ identical, \d+ modified, \d+ filtered received paths using \d+ bytes|Dampening configured, \d+ history paths, \d+ dampened paths)\z/i
|
|
20
23
|
|
|
21
24
|
private
|
|
22
25
|
|
|
@@ -58,17 +61,23 @@ module Vxlan
|
|
|
58
61
|
# 解析命令表格行,保留数量及格式错误行诊断。
|
|
59
62
|
def parse_rows(text)
|
|
60
63
|
scope, prefix_columns, prefix_seen, peers = {}, nil, {}, {}
|
|
61
|
-
prefix_total = false
|
|
64
|
+
prefix_total, prefix_table, declarations = false, false, []
|
|
62
65
|
each_content_line(text) do |line, number|
|
|
63
66
|
if HEADER.match?(line)
|
|
64
67
|
@header = true
|
|
68
|
+
prefix_columns = nil
|
|
65
69
|
elsif (match = SCOPE.match(line))
|
|
66
70
|
scope = { vrf: match[1], address_family: match[2] }
|
|
67
71
|
unparsed_records(number) unless scope[:address_family] == "L2VPN EVPN"
|
|
68
72
|
prefix_columns = nil
|
|
69
73
|
elsif (match = ROUTER.match(line))
|
|
70
74
|
scope.merge!(router_id: match[1], local_as: match[2])
|
|
75
|
+
elsif (match = TABLE.match(line))
|
|
76
|
+
declarations << match.captures.map(&:to_i) if match[1]
|
|
77
|
+
elsif DETAILS.match?(line)
|
|
78
|
+
next
|
|
71
79
|
elsif (match = /\ANeighbor\s+T\s+AS\s+(?:(PfxRcd)\s+)?(Type-[1-9]\d*(?:\s+Type-[1-9]\d*)*)\z/.match(line))
|
|
80
|
+
prefix_table = true
|
|
72
81
|
prefix_total = !match[1].nil?
|
|
73
82
|
prefix_columns = match[2].split(" ").map { |column| column.downcase.delete("-").to_sym }
|
|
74
83
|
if prefix_columns.uniq != prefix_columns || (prefix_columns - PREFIX_TYPES).any?
|
|
@@ -76,16 +85,11 @@ module Vxlan
|
|
|
76
85
|
prefix_columns = nil
|
|
77
86
|
end
|
|
78
87
|
elsif prefix_columns
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
offset = prefix_total ? 4 : 3
|
|
83
|
-
if words.size == prefix_columns.size + offset && %w[I E].include?(words[1]) && words.drop(2).all? { |word| word.match?(/\A\d+\z/) } &&
|
|
84
|
-
peer && peer[:prefixes_received] && !prefix_seen.key?(peer.object_id) && peer[:remote_as] == words[2].to_i &&
|
|
85
|
-
(!prefix_total || peer[:prefixes_received] == words[3].to_i)
|
|
86
|
-
counts = words.drop(offset).map(&:to_i)
|
|
88
|
+
statistics = prefix_record(line, scope: scope, peers: peers, columns: prefix_columns, total: prefix_total)
|
|
89
|
+
if statistics && !prefix_seen.key?(statistics.first.object_id)
|
|
90
|
+
peer, counts = statistics
|
|
87
91
|
# 路由类型是总前缀的子集;未展示的类型允许存在,已展示之和不能超过总数。
|
|
88
|
-
if counts.sum > peer[:prefixes_received]
|
|
92
|
+
if peer[:prefixes_received] && counts.sum > peer[:prefixes_received]
|
|
89
93
|
@issues << { code: :inconsistent_prefix_statistics, line: number }
|
|
90
94
|
else
|
|
91
95
|
peer[:prefixes_by_type] = prefix_columns.zip(counts).to_h
|
|
@@ -95,29 +99,75 @@ module Vxlan
|
|
|
95
99
|
unparsed_records(number)
|
|
96
100
|
end
|
|
97
101
|
elsif (match = ROW.match(line))
|
|
98
|
-
row =
|
|
99
|
-
|
|
100
|
-
unless row[:vrf] && row[:address_family] == "L2VPN EVPN" && ip_address?(row[:peer_ip]) && row[:version] == 4 && (1..4_294_967_295).cover?(row[:remote_as]) &&
|
|
101
|
-
row[:state].match?(/\A(?:\d+|Idle(?: \(Admin\))?|Active|Connect|OpenSent|OpenConfirm|Shutdown)\z/i)
|
|
102
|
+
row = neighbor_record(match, scope)
|
|
103
|
+
unless row
|
|
102
104
|
unparsed_records(number)
|
|
103
105
|
next
|
|
104
106
|
end
|
|
105
|
-
row[:peer_ip] = CtxParser::Values.ip_address(row[:peer_ip], allow_zone: true)
|
|
106
|
-
row[:state] = row[:state].downcase.tr(" ", "_")
|
|
107
|
-
row[:prefixes_received] = row[:state].match?(/\A\d+\z/) ? row[:state].to_i : nil
|
|
108
|
-
row[:state] = "established" if row[:prefixes_received]
|
|
109
107
|
@rows << row
|
|
110
108
|
# 统计表按完整作用域查找;重复邻居仍由最终身份检查整体隔离。
|
|
111
109
|
peers[row.values_at(:vrf, :address_family, :peer_ip)] ||= row
|
|
112
|
-
|
|
110
|
+
else
|
|
113
111
|
unparsed_records(number)
|
|
114
112
|
end
|
|
115
113
|
end
|
|
116
|
-
|
|
114
|
+
validate_peer_counts(declarations, scope)
|
|
115
|
+
if prefix_table && prefix_seen.size != @rows.size
|
|
117
116
|
@issues << { code: :missing_prefix_statistics, expected: @rows.size, observed: prefix_seen.size }
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
119
|
|
|
120
|
+
# 配置数覆盖本段全部邻居;capable 只校验自身边界,不代替逐邻居状态。
|
|
121
|
+
def validate_peer_counts(declarations, scope)
|
|
122
|
+
values = declarations.uniq
|
|
123
|
+
@issues << { code: :conflicting_peer_counts } if values.size > 1
|
|
124
|
+
values.each do |configured, capable|
|
|
125
|
+
count_mismatch(configured, @rows.size)
|
|
126
|
+
@issues << { code: :invalid_peer_counts, configured: configured, capable: capable } if capable > configured
|
|
127
|
+
end
|
|
128
|
+
@empty = true if values == [ [ 0, 0 ] ] && @rows.empty? && scope[:address_family] == "L2VPN EVPN"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# AS 0 只表示尚未建立邻居的未知身份;数值前缀总数始终表示已建立会话。
|
|
132
|
+
def neighbor_record(match, scope)
|
|
133
|
+
row = FIELDS.zip(match.captures).to_h.merge(scope)
|
|
134
|
+
INTEGERS.each { |key| row[key] = row[key].to_i }
|
|
135
|
+
return unless row[:vrf] && row[:address_family] == "L2VPN EVPN" && ip_address?(row[:peer_ip]) && row[:version] == 4 &&
|
|
136
|
+
(0..4_294_967_295).cover?(row[:remote_as]) && STATE.match?(row[:state])
|
|
137
|
+
|
|
138
|
+
row[:prefixes_received] = row[:state].match?(/\A\d+\z/) ? row[:state].to_i : nil
|
|
139
|
+
row[:remote_as] = row[:remote_as].nonzero?
|
|
140
|
+
return if row[:prefixes_received] && row[:remote_as].nil?
|
|
141
|
+
|
|
142
|
+
row[:peer_ip] = CtxParser::Values.ip_address(row[:peer_ip], allow_zone: true)
|
|
143
|
+
row[:state] = row[:prefixes_received] ? "established" : row[:state].downcase.tr(" ", "_")
|
|
144
|
+
row
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# 从末尾读取类型计数,保留多词状态,并按主表身份与总数关联统计。
|
|
148
|
+
def prefix_record(line, scope:, peers:, columns:, total:)
|
|
149
|
+
words = line.split(" ")
|
|
150
|
+
return unless words.size >= columns.size + (total ? 4 : 3)
|
|
151
|
+
|
|
152
|
+
counts = words.pop(columns.size)
|
|
153
|
+
address, kind, asn = words.shift(3)
|
|
154
|
+
return unless ip_address?(address) && %w[I E].include?(kind) && asn.match?(/\A\d+\z/) && counts.all? { |value| value.match?(/\A\d+\z/) }
|
|
155
|
+
|
|
156
|
+
address = CtxParser::Values.ip_address(address, allow_zone: true)
|
|
157
|
+
peer = peers[[ scope[:vrf], scope[:address_family], address ]]
|
|
158
|
+
return unless peer && peer[:remote_as] == asn.to_i.nonzero?
|
|
159
|
+
|
|
160
|
+
if total
|
|
161
|
+
value = words.join(" ")
|
|
162
|
+
return unless STATE.match?(value)
|
|
163
|
+
observed = value.match?(/\A\d+\z/) ? value.to_i : value.downcase.tr(" ", "_")
|
|
164
|
+
return unless observed == (peer[:prefixes_received] || peer[:state])
|
|
165
|
+
else
|
|
166
|
+
return unless words.empty?
|
|
167
|
+
end
|
|
168
|
+
[ peer, counts.map(&:to_i) ]
|
|
169
|
+
end
|
|
170
|
+
|
|
121
171
|
# 归一化平台值,同时保留未知或不支持的状态。
|
|
122
172
|
def normalize(text)
|
|
123
173
|
output = []
|