vxlan 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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE +22 -0
  4. data/README-zh.md +90 -0
  5. data/README.md +90 -0
  6. data/docs/architecture-zh.md +95 -0
  7. data/docs/release.md +13 -0
  8. data/examples/offline.rb +16 -0
  9. data/lib/vxlan/bgp_neighbors.rb +29 -0
  10. data/lib/vxlan/cisco_nxos/configuration.rb +84 -0
  11. data/lib/vxlan/cisco_nxos/configuration_document.rb +31 -0
  12. data/lib/vxlan/cisco_nxos/configuration_parsing/interfaces.rb +91 -0
  13. data/lib/vxlan/cisco_nxos/configuration_parsing/policies.rb +58 -0
  14. data/lib/vxlan/cisco_nxos/configuration_parsing/routing.rb +62 -0
  15. data/lib/vxlan/cisco_nxos/evpn_l2route.rb +118 -0
  16. data/lib/vxlan/cisco_nxos/parser.rb +105 -0
  17. data/lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb +80 -0
  18. data/lib/vxlan/cisco_nxos/parsers/show_cdp_neighbors_detail.rb +86 -0
  19. data/lib/vxlan/cisco_nxos/parsers/show_interface_brief.rb +84 -0
  20. data/lib/vxlan/cisco_nxos/parsers/show_ip_ospf_neighbors.rb +48 -0
  21. data/lib/vxlan/cisco_nxos/parsers/show_l2route_evpn_mac_all.rb +14 -0
  22. data/lib/vxlan/cisco_nxos/parsers/show_l2route_evpn_mac_ip_all.rb +14 -0
  23. data/lib/vxlan/cisco_nxos/parsers/show_mac_address_table.rb +42 -0
  24. data/lib/vxlan/cisco_nxos/parsers/show_nve_peers.rb +49 -0
  25. data/lib/vxlan/cisco_nxos/parsers/show_nve_vni.rb +39 -0
  26. data/lib/vxlan/cisco_nxos/parsers/show_port_channel_summary.rb +49 -0
  27. data/lib/vxlan/cisco_nxos/parsers/show_running_config.rb +303 -0
  28. data/lib/vxlan/cisco_nxos/parsers/show_vpc_brief.rb +77 -0
  29. data/lib/vxlan/cisco_nxos/profile.rb +39 -0
  30. data/lib/vxlan/collector.rb +127 -0
  31. data/lib/vxlan/configuration.rb +20 -0
  32. data/lib/vxlan/configuration_review.rb +43 -0
  33. data/lib/vxlan/l2route_parser.rb +21 -0
  34. data/lib/vxlan/observation.rb +24 -0
  35. data/lib/vxlan/parser_registry.rb +9 -0
  36. data/lib/vxlan/prefix_list_references.rb +89 -0
  37. data/lib/vxlan/runtime_parser.rb +22 -0
  38. data/lib/vxlan/snapshot.rb +23 -0
  39. data/lib/vxlan/version.rb +6 -0
  40. data/lib/vxlan.rb +16 -0
  41. metadata +140 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d63ce72a439c443b93146031919d9c1a6959608263a755cc93b633b442cfe6d8
4
+ data.tar.gz: c7a77c3ea5d30f0ed8208a34d8a3ea2ca471fbd62c80d4fc5692f56fab76d261
5
+ SHA512:
6
+ metadata.gz: 7f6a22391a2237744aa371a3a8db3ce2f88ef1e06b229e23b3fc1dcaaa034b55a1fa83f0fa0f4f85416fa545a88f228b349d5c74c9ab2f455ba69cdb9d0617ca
7
+ data.tar.gz: 5035dc852c9e249aadb9017b5cccd273bdded71624ccbe4980d263c1590c8adbc337a55b8069766d95578e8c2fa2b48a040dd13aac2d0560e4b34e9a93db9157
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ Changes to the public contract are recorded here. Versions follow Semantic Versioning.
4
+
5
+ ## 0.1.0 (2026-10-16)
6
+
7
+ ### Added
8
+
9
+ - NX-OS configuration/runtime parsers, profiles, observations, and host-independent collection.
10
+ - Standalone RubyGems packaging under `vxlan` with the `vxlan` require path and `Vxlan` namespace.
11
+ - English and Chinese documentation, an offline example, and standalone RSpec/RuboCop development tasks.
12
+ - Linux/macOS CI, isolated package verification, and automatic RubyGems and GitHub Releases with identical artifacts.
13
+
14
+ ### Fixed
15
+
16
+ - Treat inline pagination as incomplete configuration before inferring absent VXLAN features.
17
+
18
+ ### Changed
19
+
20
+ - Cross-module dependencies resolve through RubyGems/Bundler instead of relative sibling paths.
21
+ - Motor persistence and application-specific adapters remain in the host application.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Song Huang
4
+ Copyright (c) 2026 Gatework contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README-zh.md ADDED
@@ -0,0 +1,90 @@
1
+ # vxlan
2
+
3
+ [English](README.md) | [源码](https://github.com/gatework/vxlan) | [变更记录](CHANGELOG.md)
4
+
5
+ 以纯 Ruby 解析 Cisco NX-OS VXLAN/EVPN 配置和运行观察。按命令拆分的解析器保留完整性与关联关系,供库存及审查工具使用。
6
+
7
+ ## 能力
8
+
9
+ - 接口、VRF、VLAN/VNI 映射、NVE、BGP、策略及 prefix-list 配置事实。
10
+ - CDP、NVE peer/VNI、BGP EVPN、OSPF、vPC、接口、聚合口、MAC 表及 L2RIB 运行观察。
11
+ - 平台/型号策略及公共解析组件。
12
+ - 独立命令采集和安全快照,持久化由宿主负责。
13
+
14
+ ## 安装
15
+
16
+ 要求 **Ruby 4.0.6 或更新版本**。在应用的 `Gemfile` 中添加:
17
+
18
+ ```ruby
19
+ gem "vxlan"
20
+ ```
21
+
22
+ 然后执行 `bundle install`;也可直接执行 `gem install vxlan`。Ruby 加载入口为 `vxlan`,公开命名空间为 `Vxlan`;gem 包名与 Ruby 命名空间分别维护。
23
+
24
+ 模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver)。 gemspec 的运行依赖不添加版本约束;应用应提交自己的 `Gemfile.lock`,保持部署可复现。使用本库不需要启动 Rails 或配置兄弟模块的本地路径。
25
+
26
+ 发布产物和校验值见 [GitHub Releases](https://github.com/gatework/vxlan/releases),已发布版本以 [RubyGems](https://rubygems.org/gems/vxlan) 为准。
27
+
28
+ ## 离线示例
29
+
30
+ 在开发检出目录安装依赖后执行:
31
+
32
+ ```sh
33
+ bundle exec ruby examples/offline.rb
34
+ ```
35
+
36
+ 示例使用合成数据,不访问网络。完整代码见 [examples/offline.rb](examples/offline.rb):
37
+
38
+ ```ruby
39
+ require "vxlan"
40
+
41
+ # 使用合成 NX-OS OSPF 回显;文档地址不作为实时目标。
42
+ output = <<~CLI
43
+ OSPF Process ID UNDERLAY VRF default
44
+ Total number of neighbors: 1
45
+ Neighbor ID Pri State Up Time Address Interface
46
+ 192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
47
+ CLI
48
+ observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
49
+ puts "OSPF parse status: #{observation.status}"
50
+ puts "Neighbors: #{observation.records.size}"
51
+ observation.records.each { |record| p record }
52
+ # 单个已解析邻居仅是一项观测,不能证明整个 Fabric 健康。
53
+ ```
54
+
55
+ ## 公开契约与限制
56
+
57
+ `Configuration.new.parse(text, complete: false)` 返回配置观察。`RuntimeParser.new(platform: :cisco_nxos, model: nil).parse(name, text, complete: false)` 返回具名运行观察。`ParserRegistry.fetch(platform, model:)` 提供按命令解析的声明式策略。
58
+
59
+ 传输完整是解析完整的必要条件,不是充分条件。未知语法、身份冲突、不完整引用和无法证明为空的表格保留诊断。配置关联核查与语法完整性、运行健康分别表达。IPv6 prefix-list 事实不代表 IPv6 underlay 库存能力。
60
+
61
+ `Collector#collect(connector:, target:, fabric_key:, model: nil, dimensions:)` 使用宿主提供的 connector;设备身份、凭据、持久化、库存对账和并发策略由宿主负责。本 gem 不包含 fabric 规划、配置下发、Rails 模型或 `Vxlan::Sync`。加载库或运行离线示例不会访问设备。
62
+
63
+ 详细厂商语义、历史验证记录和 Motor 专用集成说明保留在[架构参考](docs/architecture-zh.md)。其中宿主集成代码需要 Motor,不属于独立 gem API。适配层位于 [Motor 仓库](https://github.com/gatework/motor/tree/main/backend/lib) 的 `backend/lib/baseline`、`backend/lib/vxlan` 和 `backend/lib/wireless`。
64
+
65
+ 生命周期 connector 是宿主适配器,提供 `open { |session| }`,VXLAN 和 Wireless 还要求 `platform`;原生 `Net::Connector::Base` 实例并不提供这一宿主生命周期协议。已连接的原生会话可传入 `CtxDriver::Reader` 或 `CtxChecker::Collector#collect_session`,不会隐式连接或重试。
66
+
67
+ ## 开发与测试
68
+
69
+ ```sh
70
+ bundle install
71
+ bundle exec rake
72
+ bundle exec ruby examples/offline.rb
73
+ bundle exec rake build
74
+ ```
75
+
76
+ `bundle exec rake` 执行 RSpec、RuboCop 及离线示例。测试放在 `spec/`,沿用项目 RSpec 约定;样例采用合成或脱敏证据。新增解析格式时补充成功、不完整、不支持及非法输入回归。离线测试不代表所有设备版本均通过实机验证。
77
+
78
+ 使用声明的最低 Ruby 版本检查兼容性。方法用途统一使用简明中文注释;复杂实现还需说明业务规则、实现思路及关键分支。保持公开返回值与错误契约。真实回显和凭据不可进入示例、fixture、日志或发布包。
79
+
80
+ ## 发布
81
+
82
+ 本目录可独立维护为 [vxlan](https://github.com/gatework/vxlan) 仓库。更新 `VERSION` 和 `CHANGELOG.md`,运行上述验证与构建命令,检查包内文件,再创建 `vVERSION` 标签。
83
+
84
+ CI 在 Linux、macOS 上执行测试、lint、示例、构建和隔离安装。`main` 的 CI 通过后推送 `vVERSION` 标签,工作流将已验证的 Linux 产物发布到 RubyGems 和 GitHub Release,附带 `SHA256SUMS` 并回读下载字节。GitHub `release` environment 可配置 `RUBYGEMS_API_KEY` secret;未配置时使用 RubyGems Trusted Publishing,绑定本仓库、`release.yml` 和 `release` environment。失败时重跑失败的发布 job,以复用原 CI 产物;同版本已存在时必须校验字节一致。详见[发布说明](docs/release.md)。
85
+
86
+ 先发布依赖模块。标签、构建成功或工作流文件都不等于已经发布,需单独确认 RubyGems 的实际结果。版本变化见 [CHANGELOG.md](CHANGELOG.md)。
87
+
88
+ ## 许可证
89
+
90
+ MIT,见 [LICENSE](LICENSE)。
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # vxlan
2
+
3
+ [简体中文](README-zh.md) | [Source](https://github.com/gatework/vxlan) | [Changelog](CHANGELOG.md)
4
+
5
+ Cisco NX-OS VXLAN/EVPN configuration and operational observations in pure Ruby. Command-specific parsers preserve evidence completeness and relationships needed by inventory or review tools.
6
+
7
+ ## Features
8
+
9
+ - Configuration facts for interfaces, VRFs, VLAN/VNI mappings, NVE, BGP, policies, and prefix lists.
10
+ - Runtime observations for CDP, NVE peers/VNIs, BGP EVPN, OSPF, vPC, interfaces, port channels, MAC tables, and L2RIB.
11
+ - Platform/model profiles and shared parsing infrastructure.
12
+ - Independent command collection and safe snapshots for host-owned persistence.
13
+
14
+ ## Installation
15
+
16
+ Requires **Ruby 4.0.6 or newer**. Add this line to your application's `Gemfile`:
17
+
18
+ ```ruby
19
+ gem "vxlan"
20
+ ```
21
+
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
+
24
+ Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver). The gemspec declares runtime dependencies without version constraints. Applications should commit their own `Gemfile.lock` to make deployments reproducible. No Rails boot or sibling checkout paths are required.
25
+
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
+
28
+ ## Offline example
29
+
30
+ From a development checkout after installing dependencies:
31
+
32
+ ```sh
33
+ bundle exec ruby examples/offline.rb
34
+ ```
35
+
36
+ The example uses synthetic data and performs no network access:
37
+
38
+ ```ruby
39
+ require "vxlan"
40
+
41
+ # 使用合成 NX-OS OSPF 回显;文档地址不作为实时目标。
42
+ output = <<~CLI
43
+ OSPF Process ID UNDERLAY VRF default
44
+ Total number of neighbors: 1
45
+ Neighbor ID Pri State Up Time Address Interface
46
+ 192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
47
+ CLI
48
+ observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
49
+ puts "OSPF parse status: #{observation.status}"
50
+ puts "Neighbors: #{observation.records.size}"
51
+ observation.records.each { |record| p record }
52
+ # 单个已解析邻居仅是一项观测,不能证明整个 Fabric 健康。
53
+ ```
54
+
55
+ ## Public contract and limits
56
+
57
+ `Configuration.new.parse(text, complete: false)` returns a configuration observation. `RuntimeParser.new(platform: :cisco_nxos, model: nil).parse(name, text, complete: false)` returns a named runtime observation. `ParserRegistry.fetch(platform, model:)` exposes a declared profile for command-specific parsing.
58
+
59
+ Complete transport is necessary but insufficient for complete parsing: unknown syntax, conflicting identities, incomplete references, and ambiguous empty tables retain diagnostics. Configuration association findings are separate from syntax completeness and runtime health. IPv6 prefix-list facts do not imply IPv6 underlay inventory support.
60
+
61
+ `Collector#collect(connector:, target:, fabric_key:, model: nil, dimensions:)` uses a host-supplied connector; device identity, credentials, persistence, reconciliation, and concurrency policy belong to the host. This gem includes no fabric planner, provisioning engine, Rails models, or `Vxlan::Sync`. Loading or running the offline example does not access a device.
62
+
63
+ Detailed vendor behavior, historical validation notes, and Motor-only integration are preserved in [the architecture reference](docs/architecture-zh.md). Host integration examples in that reference require Motor; they are not standalone gem APIs. Motor adapters live under `backend/lib/baseline`, `backend/lib/vxlan`, and `backend/lib/wireless` in the [Motor repository](https://github.com/gatework/motor/tree/main/backend/lib).
64
+
65
+ 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.
66
+
67
+ ## Development and tests
68
+
69
+ ```sh
70
+ bundle install
71
+ bundle exec rake
72
+ bundle exec ruby examples/offline.rb
73
+ bundle exec rake build
74
+ ```
75
+
76
+ `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.
77
+
78
+ 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.
79
+
80
+ ## Release
81
+
82
+ This directory can be maintained as the independent [vxlan](https://github.com/gatework/vxlan) repository. Update `VERSION` and `CHANGELOG.md`, run the validation and build commands above, and inspect the packaged file list before tagging `vVERSION`.
83
+
84
+ CI runs tests, lint, examples, build, and an isolated installation on Linux and macOS. After CI passes on `main`, push `vVERSION` to run the release workflow. It publishes the tested Linux artifact to RubyGems and GitHub Releases, attaches `SHA256SUMS`, and verifies downloaded bytes. The GitHub `release` environment accepts a `RUBYGEMS_API_KEY` secret; without it, configure RubyGems Trusted Publishing for this repository, `release.yml`, and the `release` environment. Retry a failed publish job to reuse its CI artifact; an already published version must have identical bytes. See [release details](docs/release.md).
85
+
86
+ Publish dependency gems first. A Git tag, a successful build, or a workflow file does not establish that the gem has reached RubyGems; confirm the registry result separately. See [CHANGELOG.md](CHANGELOG.md) for release notes.
87
+
88
+ ## License
89
+
90
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,95 @@
1
+ > 本文保留模块从 Motor 提取前的领域设计、覆盖范围、限制与验证记录,供兼容迁移和故障分析使用。
2
+ > 当前独立 gem 的安装、依赖、开发与发布入口以 [README-zh.md](../README-zh.md) 为准。
3
+ > 涉及 `Motor::*`、`Sync`、`Baseline::Check`、数据库、Rails、Rake 运维任务及私有实机证据的内容均属于 Motor 宿主集成,不随 gem 提供;历史验证记录不代表当前用户设备已经验证。
4
+
5
+ # VXLAN 设备采集与入库
6
+
7
+ 这个目录保存 Motor 自有的 VXLAN 业务实现,参考 Netstack 的 NX-OS 能力和已验证的完整性规则,提供配置关联核验,不搬入 Fabric 规划、报表、CLI 或远程下发引擎。Rails 通过 `config.paths` 自动加载 `lib/vxlan`;数据模型仍使用现有 `Motor::Vxlan*` 类和 Rails 可推导的关联。不新增数据表;prefix-list 以节点、名称、地址族作为唯一身份,已有记录默认 IPv4。
8
+
9
+ ## 业务链路
10
+
11
+ ```text
12
+ Motor::Device
13
+ → Motor::Devices::Connector(凭据、平台、严格主机密钥、超时、会话生命周期)
14
+ → CtxDriver::Reader(net-connector 单命令采集边界)
15
+ → Vxlan::Collector(同一设备逐条读取)
16
+ → Configuration / RuntimeParser / L2routeParser(维度与 Observation 适配)
17
+ → CiscoNxos::Profile / Parsers::Show*(平台与型号策略、按命令解析)
18
+ → Snapshot / Observation
19
+ → Vxlan::Sync(锁设备、复核来源与版本、事务)
20
+ → ConfigurationInventory / L2routeInventory / vxlan_segments
21
+ ```
22
+
23
+ 采集通过 [`CtxDriver::Reader`](https://github.com/gatework/ctx_driver) 使用 net-connector 的 `send_commands`,统一验证单命令响应、记录来源摘要并隔离失败;不自己实现 SSH、提示符处理或登录。单脚本解析异常不丢弃已完成维度,断线后标记剩余命令 skipped。配置下发继续由 `Motor::Devices::Script` 和 Netconf 的审批快照、执行认领及设备复核负责;执行开始后的未知结果保留为未知,不自动重放。采集任务不隐式下发配置、自动保存 running-config 或修改设备配置。
24
+
25
+ ## 调用
26
+
27
+ ```ruby
28
+ result = Vxlan::Sync.new.call(device: Motor::Device.find(device_id), fabric_key: "LAB")
29
+ result.counts
30
+ result.observation.complete?
31
+
32
+ # 仅刷新两类 L2RIB 路由;其他维度保持原有证据和数据。
33
+ Vxlan::Sync.new.call(device: device, fabric_key: "LAB",
34
+ dimensions: %i[l2route_macs l2route_mac_ips])
35
+
36
+ # 离线解析不建立设备会话,不写数据库。
37
+ observation = Vxlan::Configuration.new.parse(configuration_text, complete: true)
38
+ observation.status
39
+ observation.records
40
+ ```
41
+
42
+ 保留已有运维入口,避免现有调用脚本失效:
43
+
44
+ ```sh
45
+ bundle exec rake 'motor:netstack:vxlan[456,LAB]'
46
+ ```
47
+
48
+ 默认支持 Cisco NX-OS 的配置、CDP、NVE peer/VNI、BGP EVPN 邻居、OSPF 邻居、vPC、接口、Port-channel、MAC 地址表、L2RIB MAC 和 MAC-IP。命令集中在 `Collector::COMMANDS`。新增设备输出格式时,应先补脱敏 fixture 和完整/截断用例,再修改对应解析器;不能把未知输出判定为空结果。
49
+
50
+ Cisco N9K 的具体策略位于 [`lib/vxlan/cisco_nxos`](../lib/vxlan/cisco_nxos/profile.rb),按命令拆分为 `parsers/show_ip_ospf_neighbors.rb` 等独立文件。直接调用 `Vxlan::CiscoNxos::Parsers::ShowIpOspfNeighbors.parse(cli_output, complete: true)` 即可离线解析。`ParserRegistry.fetch(:cisco_nxos, model: device.model)` 选择平台与型号策略;命令清单和解析类统一在 `CiscoNxos::Profile` 中声明。原解析入口继续转换为 `Vxlan::Observation`。公共接口、结果对象和扩展方式见 [`../ctx_parser`](https://github.com/gatework/ctx_parser)。
51
+
52
+ 配置解析按职责组合:`ShowRunningConfig` 编排命令解析,`ConfigurationParsing::Interfaces/Routing/Policies` 分别解释接口/NVE、路由上下文及策略语法,`CtxParser::ConfigurationParser` 管理逐行状态、嵌套来源路径与捕获,`CiscoNxos::Configuration` 在全部块解析后归约特性列表、校验 BGP 模板引用并关联 SVI/VLAN/VNI 网段。接口地址与网段使用 `CtxParser::Values.ipv4_prefix` 的同一校验。重复身份仍排除歧义记录;语法未知、引用不完整与来源行号继续进入原有 `Result`,供入库边界判断。
53
+
54
+ 设备连接默认关闭旧版 SSH 算法协商。确认单台旧设备确有算法兼容需求后,可在其现有 `settings["connector"]["legacy_ssh"]` 中显式保存布尔值 `true`;省略或 `false` 保持关闭,字符串、NULL 等非布尔值会在连接前拒绝。该设置直接传给 net-connector,主机密钥仍严格核验 `storage/net_connector/known_hosts`,不启用 Telnet 回退。连接异常仅保留允许列表中的诊断码(例如 `unsupported_host_key_algorithm`),未知码归为 `connection_failed`;VXLAN 未采集维度保存该原因,不保存底层异常文案或 cause。
55
+
56
+ ## 数据一致性
57
+
58
+ 设备 I/O 在数据库事务外完成。入库时锁定已有设备,复核设备版本、节点来源、Fabric 及原快照;并发期间已变更的设备或节点、旧结果和同时间不同结果均拒绝写入。节点证据和所有派生清单在同一事务提交,任何写入失败全部回滚。重复投递相同快照保持现有行身份,也可重建缺失的派生行。
59
+
60
+ 配置的 vPC、Underlay、Overlay、VNI、features、prefix-list 只在完整配置时刷新。三层网段同样只在完整配置时更新和清理。部分配置保存在节点证据中,不能把未读到的 anycast、ACL 或被截断的前缀覆盖到权威清单。L2RIB 两种路由各自判定完整性,部分结果保留旧可选字段并合并下一跳,完整结果才清理该类型缺失行。配置失败不阻止已成功采集的运行路由入库。
61
+
62
+ `vxlan_nodes.observations` 的每个维度保留 `latest`;新结果不完整时额外保留 `last_complete`。未请求维度不变。新 VXLAN 观察统一使用 `name/status/records/issues/evidence/started_at/completed_at`;既有历史 JSON 无需回填,后续按维度替换,旧 `last_complete` 保持原形状。展示方须同时显示本次状态和旧数据时间,不能把保留数据显示成实时成功。无线与 VXLAN 共用 `Motor::InventoryObservations` 的时间比较和证据合并规则。
63
+
64
+ 配置中的凭据及原始 CLI 正文不持久化。命令证据只保存命令名、字节数和摘要,诊断不附原始配置行。`complete: true` 表示调用者已确认完整接收;解析器仍会因未知语法、歧义身份或不完整输出降为 `partial`。单有表头不能证明空清单。
65
+
66
+ ## 验证
67
+
68
+ ```sh
69
+ RAILS_ENV=test bundle exec rspec spec/vendor/vxlan spec/lib/motor/netstack \
70
+ spec/services/motor/devices/script_spec.rb spec/services/motor/netconf/executor_spec.rb
71
+ RAILS_ENV=test bundle exec rails zeitwerk:check
72
+ bundle exec rubocop
73
+ ```
74
+
75
+ 测试覆盖离线解析、实际数据库投影、并发冲突及替身设备会话。另有 2026-10-16 全量 83 台 N9K 的真实只读采集及离线重放证据,保存在本机私有 `storage/network-audits`,不把包含凭据的真实配置提交到仓库。
76
+
77
+ ## 配置覆盖与关联核验
78
+
79
+ NVE 的内联 `member vni ... mcast-group ...` 与嵌套组播声明归一为相同事实;全局 multicast default 仅用于没有显式组播、BGP 复制或 L3 关联的成员。无效组播、冲突成员和未知语法保持 partial。`interface breakout` 单独进入 `breakouts`,不伪装成接口;`no feature` 进入 `disabled_features`。
80
+
81
+ 接口事实包含 MTU、OSPF network type、switchport 模式/access VLAN、L3 VNI `ip forward`;NVE 和 BGP 保留 `advertise virtual-rmac`、`advertise-pip`、`advertise l2vpn evpn`、标准/扩展社区和显式关闭状态。BGP 邻居描述、route-map 的方向和 next-hop-self 的 all 语义保留在正确的 VRF/邻居/地址族中。
82
+
83
+ `BgpNeighbors` 为 Overlay 投影和策略引用共用模板归约,显式子邻居配置覆盖继承方向;不同 VRF、邻居和地址族的调用位置保持独立。Prefix-list 支持 IPv4/IPv6、address/next-hop/route-source 选择器及邻居/network/重分发/retain/export 调用关系。IPv4 事实继续位于 `prefix_lists`,IPv6 位于 `ipv6_prefix_lists`,历史观察无需重写。
84
+
85
+ `ip unnumbered` 保留借用接口关系;同 VRF 的唯一 Loopback IPv4 地址归约到 `unnumbered_addresses`。Underlay 清单使用该物理接口和借用的 /32 主机地址,不猜测链路子网。缺失、歧义、循环或跨 VRF 引用保持 partial;IPv6 Underlay 未被伪装成已支持的 IPv4 清单。
86
+
87
+ NX-OS 的 `ConfigurationDocument` 与 Baseline 共用 `CtxParser::Banner` 状态机,banner 正文不会生成配置事实;EOF 未闭合会产生 `incomplete_banner`。
88
+
89
+ 完整配置的 `review_findings` 输出 VLAN/VNI/NVE/EVPN 关联核查项,不改变语法完整性,也不直接表示运行故障。`review_status` 为 partial 时不根据缺失数据生成关联结论。这样保留“配置收到且语法已理解”和“业务关联需要核实”两个事实。
90
+
91
+ ## Prefix-list 地址族迁移
92
+
93
+ 迁移 `20261016020000` 增加 `address_family`,默认 `ipv4`,由模型和 PostgreSQL 同时约束到 IPv4/IPv6;组合唯一索引允许双栈同名列表。RailsAdmin 列表和详情显示地址族,并提供中英文标签。
94
+
95
+ 部署时先排空旧 VXLAN 采集 worker,执行迁移并切换到新代码后再恢复采集。旧代码只按名称定位列表,不应与已经写入双栈数据的新 worker 重叠。迁移会保留原行身份和数据;存在 IPv6 历史时 rollback 明确拒绝删除地址族,需先导出并协调处理。当前本机 development/test 已执行迁移。
data/docs/release.md ADDED
@@ -0,0 +1,13 @@
1
+ # Release
2
+
3
+ 1. Update `lib/vxlan/version.rb` and `CHANGELOG.md`; run `bundle exec rake` and `bundle exec rake build`.
4
+ 2. Run `ruby script/verify_package.rb pkg/vxlan-VERSION.gem`. This installs and loads the built gem outside Bundler and the checkout.
5
+ 3. Commit and push `main`; wait for both Linux and macOS CI jobs.
6
+ 4. Push the matching `vVERSION` tag. The Release workflow repeats CI, then publishes its exact Linux artifact to RubyGems and GitHub with `SHA256SUMS`.
7
+ 5. Verify the workflow, registry version, tag commit, and downloaded checksums. A local build is not publication evidence.
8
+
9
+ Configure `RUBYGEMS_API_KEY` as an encrypted secret in the GitHub `release` environment, with noninteractive gem-push permission. Alternatively configure [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for `gatework/vxlan`, `release.yml`, environment `release`, and omit that secret. Never commit credentials.
10
+
11
+ A failed publish is not permission to overwrite an existing version. Rerun the failed publish job to retain the original CI artifact. The script verifies an existing RubyGems version before proceeding and rejects different bytes or yanked versions. `workflow_dispatch` is restricted to version tags by the publish validation.
12
+
13
+ Raw device `*.log`, local secrets, and build outputs are ignored. Package files use an explicit gemspec allowlist; review both the Git index and gem contents before publishing. Tests and examples must use synthetic or sanitized evidence.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "vxlan"
4
+
5
+ # 使用合成 NX-OS OSPF 回显;文档地址不作为实时目标。
6
+ output = <<~CLI
7
+ OSPF Process ID UNDERLAY VRF default
8
+ Total number of neighbors: 1
9
+ Neighbor ID Pri State Up Time Address Interface
10
+ 192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
11
+ CLI
12
+ observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
13
+ puts "OSPF parse status: #{observation.status}"
14
+ puts "Neighbors: #{observation.records.size}"
15
+ observation.records.each { |record| p record }
16
+ # 单个已解析邻居仅是一项观测,不能证明整个 Fabric 健康。
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vxlan
4
+ # 统一解析对等体模板,供清单投影与策略引用共用。
5
+ class BgpNeighbors
6
+ include Enumerable
7
+
8
+ # 绑定需要解析对等体模板继承关系的 BGP 配置。
9
+ def initialize(bgp)
10
+ @bgp = bgp
11
+ end
12
+
13
+ # 应用继承的对等体模板配置后,逐个返回实际生效的 BGP 邻居。
14
+ def each
15
+ return enum_for(:each) unless block_given?
16
+
17
+ templates = @bgp.fetch(:templates, {})
18
+ { "default" => @bgp }.merge(@bgp.fetch(:vrfs, {})).each do |vrf, scope|
19
+ scope.fetch(:neighbors, {}).each do |peer, child|
20
+ parent = templates.fetch(child[:inherit_peer], {})
21
+ families = parent.fetch(:address_families, {}).merge(child.fetch(:address_families, {})) do |_name, inherited, explicit|
22
+ inherited.merge(explicit.compact).merge(route_maps: inherited.fetch(:route_maps, {}).merge(explicit.fetch(:route_maps, {})))
23
+ end
24
+ yield vrf, peer, parent.merge(child.compact).merge(address_families: families)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ctx_parser/values"
4
+
5
+ module Vxlan
6
+ module CiscoNxos
7
+ # 归纳 NX-OS 模板引用、功能身份及 SVI、VLAN、VNI 子网关系。
8
+ class Configuration
9
+ attr_reader :data, :issues
10
+
11
+ # 初始化单次配置解析使用的可变 NX-OS 事实容器。
12
+ def initialize
13
+ @data = { hostname: nil, features: [], disabled_features: [], breakouts: {}, interfaces: {}, vlan_vnis: {}, vrfs: {}, nve: { members: {} },
14
+ evpn: {}, bgp: {}, ospf: {}, vpc_domain: nil, unnumbered_addresses: {}, prefix_lists: {}, ipv6_prefix_lists: {}, route_maps: {}, acls: {}, static_routes: [] }
15
+ @issues = []
16
+ end
17
+
18
+ # 解析结束后关联跨块引用,避免声明顺序影响结果。
19
+ def finish
20
+ validate_templates
21
+ resolve_unnumbered
22
+ issues << { code: :conflicting_feature_state } if (data[:features] & data[:disabled_features]).any?
23
+ issues << { code: :hostname_missing } unless data[:hostname]
24
+ data[:features] = data[:features].uniq.sort
25
+ data[:disabled_features] = data[:disabled_features].uniq.sort
26
+ data[:segments] = segments
27
+ data
28
+ end
29
+
30
+ # 展示对象身份与状态,避免泄露原始回显或凭据。
31
+ def inspect = "#<#{self.class} interfaces=#{data[:interfaces].size} issues=#{issues.size}>"
32
+
33
+ private
34
+
35
+ # 借用的 IPv4 地址仅作为主机身份,不猜测链路网段。
36
+ def resolve_unnumbered
37
+ interfaces = data[:interfaces]
38
+ by_name = interfaces.group_by { |name, _values| name.downcase }
39
+ interfaces.each do |name, values|
40
+ next unless values[:unnumbered_interface]
41
+
42
+ sources = by_name.fetch(values[:unnumbered_interface].downcase, [])
43
+ source = sources.one? ? sources.first.last : nil
44
+ if source && source[:addresses].one? && values[:addresses].empty? &&
45
+ !source[:unnumbered_interface] && source.fetch(:vrf, "default") == values.fetch(:vrf, "default")
46
+ data[:unnumbered_addresses][name] = [ "#{source[:addresses].first.split('/').first}/32" ]
47
+ else
48
+ issues << { code: :unresolved_unnumbered_interface, interface: name }
49
+ end
50
+ end
51
+ end
52
+
53
+ # 报告未解析的继承模板,不虚构模板配置。
54
+ def validate_templates
55
+ bgp = data[:bgp]
56
+ templates = bgp.fetch(:templates, {})
57
+ issues << { code: :unsupported_peer_inheritance } if templates.values.any? { |settings| settings[:inherit_peer] }
58
+ [ bgp, *bgp.fetch(:vrfs, {}).values ].each do |scope|
59
+ scope.fetch(:neighbors, {}).each_value do |neighbor|
60
+ next unless neighbor[:inherit_peer] && !templates.key?(neighbor[:inherit_peer])
61
+
62
+ issues << { code: :missing_peer_template }
63
+ end
64
+ end
65
+ end
66
+
67
+ # 根据显式配置构建 VLAN 与 VNI 网段关联。
68
+ def segments
69
+ data[:interfaces].filter_map do |name, values|
70
+ match = /\AVlan(\d+)\z/.match(name)
71
+ next unless match && data[:vlan_vnis][match[1].to_i] && values[:addresses].any?
72
+ unless values[:addresses].one? && values[:vrf]
73
+ issues << { code: :unsupported_svi, interface: name }
74
+ next
75
+ end
76
+ source = values[:addresses].first
77
+ { vlan_id: match[1].to_i, vni: data[:vlan_vnis].fetch(match[1].to_i), vrf: values[:vrf],
78
+ prefix: CtxParser::Values.ipv4_prefix(source), gateway: source.split("/").first,
79
+ anycast: values[:anycast], acl_bindings: values[:acl_bindings] }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ctx_parser/document"
4
+ require "ctx_parser/banner"
5
+
6
+ module Vxlan
7
+ module CiscoNxos
8
+ # NX-OS 横幅始终作为词法文本处理,即使其正文看起来像命令。
9
+ class ConfigurationDocument < CtxParser::Document
10
+ protected
11
+
12
+ # 区分配置结构行与引号、横幅中的文本内容。
13
+ def classify(line)
14
+ @banner ||= CtxParser::Banner.new
15
+ return :ignored if @banner.consume(line.text)
16
+
17
+ if /--\s*More\s*--|\e\[/i.match?(line.text)
18
+ issues << { code: :incomplete_configuration, line: line.number }
19
+ return :ignored
20
+ end
21
+
22
+ super
23
+ end
24
+
25
+ # 报告配置文档结束时仍未闭合的横幅。
26
+ def finish
27
+ issues << { code: :incomplete_banner } if @banner&.open?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vxlan
4
+ module CiscoNxos
5
+ module ConfigurationParsing
6
+ # 接口与 NVE 语法共用命令解析器的来源边界。
7
+ module Interfaces
8
+ private
9
+
10
+ # 解析接口块及其显式配置属性。
11
+ def interface(block)
12
+ values = { addresses: [], acl_bindings: {}, anycast: false }
13
+ analyze_children(block) do |child|
14
+ case child.text
15
+ when /\Aip address (\S+)(?: secondary)?\z/ then values[:addresses] << prefix($1, host: true)
16
+ when /\Aip unnumbered (loopback\d+)\z/i then values[:unnumbered_interface] = $1
17
+ when /\Avrf member (\S+)\z/ then values[:vrf] = $1
18
+ when /\Aip router ospf (\S+) area (\S+)\z/ then values[:ospf] = "#{$1} area #{$2}"
19
+ when /\Aip ospf network (point-to-point|broadcast)\z/ then values[:ospf_network_type] = $1
20
+ when /\Amtu (\d+)\z/ then values[:mtu] = integer($1, 1..65_535)
21
+ when /\Aswitchport mode (access|trunk|dot1q-tunnel|fex-fabric)\z/ then values[:switchport_mode] = $1
22
+ when /\Aswitchport access vlan (\d+)\z/ then values[:access_vlan] = integer($1, 1..4094)
23
+ when "switchport", "no switchport" then values[:switchport] = child.text == "switchport"
24
+ when "ip forward", "no ip forward" then values[:ip_forward] = child.text == "ip forward"
25
+ when "fabric forwarding mode anycast-gateway" then values[:anycast] = true
26
+ when /\Aip access-group (\S+) (in|out)\z/ then values[:acl_bindings][$2.to_sym] = $1
27
+ when /\Avpc (\d+)\z/ then values[:vpc_id] = integer($1, 1..4096)
28
+ when "vpc peer-link" then values[:peer_link] = true
29
+ when /\Achannel-group (\d+)(?: mode \S+)?\z/ then values[:channel_group] = integer($1, 1..4096)
30
+ when /\Adescription (.+)\z/ then values[:description] = $1
31
+ when "shutdown" then values[:shutdown] = true
32
+ when "no shutdown" then values[:shutdown] = false
33
+ else check_unhandled(child)
34
+ end
35
+ end
36
+ values
37
+ end
38
+
39
+ # 解析 NVE 接口设置与成员 VNI 声明。
40
+ def nve(block)
41
+ values = { members: {} }
42
+ analyze_children(block) do |child|
43
+ case child.text
44
+ when /\Amember vni (\d+)(?: (associate-vrf)| mcast-group (\S+))?\z/
45
+ id, associated, group = integer($1, 1..16_777_215), !$2.nil?, $3
46
+ member = { associate_vrf: associated }
47
+ member[:multicast_group] = multicast_address(group) if group
48
+ analyze_children(child) { |setting| nve_member(setting, member) }
49
+ raise ArgumentError if member[:replication] == "bgp" && member[:multicast_group]
50
+
51
+ store(values[:members], id, child, path: @source_path + [ :members, id ]) { member }
52
+ when /\Adescription (.+)\z/ then values[:description] = $1
53
+ when /\Aglobal mcast-group (\S+)\z/ then values[:multicast_group] = multicast_address($1)
54
+ when /\Asource-interface (\S+)\z/ then values[:source_interface] = $1
55
+ when /\Ahost-reachability protocol (\S+)\z/ then values[:host_reachability] = $1
56
+ when "shutdown" then values[:shutdown] = true
57
+ when "no shutdown" then values[:shutdown] = false
58
+ when "advertise virtual-rmac", "no advertise virtual-rmac"
59
+ values[:advertise_virtual_rmac] = child.text == "advertise virtual-rmac"
60
+ else issue(child)
61
+ end
62
+ end
63
+ values
64
+ end
65
+
66
+ # 解析单个 NVE 成员 VNI 及其复制方式或 VRF 属性。
67
+ def nve_member(setting, member)
68
+ case setting.text
69
+ when "ingress-replication protocol bgp" then member[:replication] = "bgp"
70
+ when /\Amcast-group (\S+)\z/
71
+ group = multicast_address($1)
72
+ raise ArgumentError if member[:multicast_group] && member[:multicast_group] != group
73
+
74
+ member[:multicast_group] = group
75
+ when "suppress-arp" then member[:suppress_arp] = true
76
+ when "no suppress-arp" then member[:suppress_arp] = false
77
+ else issue(setting)
78
+ end
79
+ end
80
+
81
+ # 校验 VXLAN 复制使用的组播地址。
82
+ def multicast_address(value)
83
+ group = address(value)
84
+ raise ArgumentError unless IPAddr.new("224.0.0.0/4").include?(group)
85
+
86
+ group
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end