vxlan 0.2.2 → 0.3.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 +74 -0
- data/README-zh.md +32 -4
- data/README.md +32 -4
- data/bin/README.md +203 -0
- data/bin/vxlan +8 -0
- data/docs/architecture-zh.md +62 -60
- data/docs/evidence-contracts.md +54 -0
- data/docs/remediation.md +84 -0
- data/docs/review-2026-10-19.md +31 -0
- data/docs/superpowers/plans/2026-10-19-command-line.md +69 -0
- data/docs/superpowers/plans/2026-10-19-connector-integration.md +83 -0
- data/docs/superpowers/plans/2026-10-19-evidence-contracts.md +64 -0
- data/docs/superpowers/plans/2026-10-19-vxlan-examples.md +47 -0
- data/docs/superpowers/plans/2026-10-20-fabric-consistency.md +28 -0
- data/docs/superpowers/specs/2026-10-19-command-line-design.md +78 -0
- data/docs/superpowers/specs/2026-10-19-connector-integration-design.md +100 -0
- data/docs/superpowers/specs/2026-10-19-evidence-contracts.md +11 -0
- data/examples/README.md +20 -0
- data/examples/collect.rb +8 -0
- data/examples/parse.rb +5 -0
- data/lib/vxlan/acl_policy.rb +50 -0
- data/lib/vxlan/change_plan/svi_acl.rb +76 -0
- data/lib/vxlan/change_plan/svi_gateway.rb +36 -0
- data/lib/vxlan/change_plan/vlan_vni.rb +40 -0
- data/lib/vxlan/change_plan.rb +131 -0
- data/lib/vxlan/cisco_nxos/configuration_state.rb +1 -4
- data/lib/vxlan/cisco_nxos/evpn_l2route.rb +2 -2
- data/lib/vxlan/cisco_nxos/parser.rb +2 -2
- data/lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb +67 -13
- data/lib/vxlan/cisco_nxos/parsers/show_cdp_neighbors_detail.rb +32 -8
- data/lib/vxlan/cisco_nxos/parsers/show_mac_address_table.rb +1 -1
- data/lib/vxlan/cisco_nxos/parsers/show_nve_peers.rb +5 -4
- data/lib/vxlan/cisco_nxos/parsers/show_nve_vni.rb +9 -1
- data/lib/vxlan/cisco_nxos/parsers/show_running_config.rb +40 -8
- data/lib/vxlan/cisco_nxos/parsers/show_vpc_brief.rb +11 -5
- data/lib/vxlan/cisco_nxos/syntax/policies.rb +16 -0
- data/lib/vxlan/cisco_nxos/syntax/routing.rb +16 -6
- data/lib/vxlan/cli/apply.rb +172 -0
- data/lib/vxlan/cli/collect.rb +210 -0
- data/lib/vxlan/cli/connector.rb +23 -0
- data/lib/vxlan/cli/options.rb +311 -0
- data/lib/vxlan/cli/parse.rb +58 -0
- data/lib/vxlan/cli/report.rb +223 -0
- data/lib/vxlan/cli/review/fabric.rb +102 -0
- data/lib/vxlan/cli/review/report.rb +165 -0
- data/lib/vxlan/cli/review.rb +125 -0
- data/lib/vxlan/cli/templates/env.example +12 -0
- data/lib/vxlan/cli/templates/vxlan.yml +32 -0
- data/lib/vxlan/cli.rb +41 -0
- data/lib/vxlan/collector.rb +59 -28
- data/lib/vxlan/config/evidence.rb +18 -0
- data/lib/vxlan/config/review.rb +56 -0
- data/lib/vxlan/{configuration.rb → config.rb} +1 -1
- data/lib/vxlan/fabric/review.rb +324 -0
- data/lib/vxlan/fabric/scope.rb +43 -0
- data/lib/vxlan/observation.rb +3 -0
- data/lib/vxlan/recommendations.rb +193 -0
- data/lib/vxlan/service/catalog.rb +77 -0
- data/lib/vxlan/service/mapping.rb +53 -0
- data/lib/vxlan/snapshot.rb +3 -0
- data/lib/vxlan/subnet_index.rb +88 -0
- data/lib/vxlan/version.rb +1 -1
- data/lib/vxlan.rb +4 -2
- metadata +137 -7
- data/lib/vxlan/cisco_nxos/configuration.rb +0 -10
- data/lib/vxlan/configuration_review.rb +0 -54
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# VXLAN examples implementation plan
|
|
2
|
+
|
|
3
|
+
> Execution: native in the current checkout, as requested by the user's “直接做”. No new commit or device operation is requested.
|
|
4
|
+
|
|
5
|
+
**Goal:** Provide practical VXLAN collection and offline parsing scripts with the configuration, help and report experience of net-connector/examples.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep Vxlan a pure observation library. Host scripts own inventory, optional net-connector, credentials, parallelism and persistence. Reuse Collector's parsing and evidence semantics and add a borrowed-session entry point so host code can explicitly own native sessions.
|
|
8
|
+
|
|
9
|
+
**Tech stack:** Ruby >=4.0.6, RSpec, stdlib OptionParser/YAML/JSON, existing ctx_parser/ctx_driver; optional net-connector and dotenv for collection examples.
|
|
10
|
+
|
|
11
|
+
## Requirements and decisions
|
|
12
|
+
|
|
13
|
+
- Core runtime dependencies must not gain net-connector or dotenv.
|
|
14
|
+
- Offline parse accepts configuration or a supported runtime dimension, a file/stdin, and explicit complete-transport declaration. Human summary by default, JSON on request.
|
|
15
|
+
- Live collect reads static devices from YAML or optional Netdisco settings, selects devices, validates all inputs before connecting, and runs devices concurrently while each device's commands remain sequential.
|
|
16
|
+
- `.env` supplies credentials without overwriting existing environment; CLI > YAML > environment defaults. Paths resolve against project root, explicit config paths against the invocation directory.
|
|
17
|
+
- `--help`, `--examples`, `--init`, `--inventory-only` must never connect to devices. Missing files offer creation only on a terminal; automation requires explicit `--init`.
|
|
18
|
+
- No passwords in YAML, generated templates or public console output. Host-key default strict. Parallel default 4, limit 500. Missing credentials fail before connections. No scan, no arbitrary query, no write commands.
|
|
19
|
+
- Reports use hostname-IP device identity, restrictive permissions, atomic per-file publication, JSON facts plus readable per-device and batch summaries. Complete evidence is not a fabric health verdict.
|
|
20
|
+
- Default Rake/package verification must execute only offline demonstrations/help, never live examples by glob.
|
|
21
|
+
|
|
22
|
+
## Tasks
|
|
23
|
+
|
|
24
|
+
- [x] 1. Collector: regression tests for borrowed sessions (no open/close), shared collection failures, configuration-first dependent queries; implement `collect_session` and reuse the path from lifecycle collection; run collector specs.
|
|
25
|
+
- [x] 2. Host examples: failing tests for CLI help without optional dependencies, config precedence, initialization/no overwrite, invalid selectors/dimensions, missing credentials, sequential fake receipts, batch isolation, reports and exit statuses. Implement `examples/parse.rb`, `examples/collect.rb`, and focused support classes under `examples/support/`.
|
|
26
|
+
- [x] 3. Docs/packaging: explicit offline example task, YAML/env templates and examples README, English/Chinese top-level usage, package allowlist and installed help checks. Verify RSpec/lint/examples, gem build, isolated installation and source runtime boundaries.
|
|
27
|
+
|
|
28
|
+
## Review focus
|
|
29
|
+
|
|
30
|
+
- Empty/malformed inventory and duplicate hosts must fail or report no targets, never silently succeed.
|
|
31
|
+
- Incomplete or unsupported evidence must retain status and issue codes; configured association issues are not runtime health.
|
|
32
|
+
- Failures during connect/command/close must preserve acquired evidence and release owned sessions.
|
|
33
|
+
- Config/env path resolution must work from examples/ and project root without reading sibling checkouts.
|
|
34
|
+
- Packaging must exclude real local credentials, device captures and generated output; helpers must not execute on load.
|
|
35
|
+
|
|
36
|
+
## Execution evidence
|
|
37
|
+
|
|
38
|
+
- Dependencies ctx_parser/ctx_driver 0.2.4 fetched from RubyGems, not neighboring checkouts.
|
|
39
|
+
- Red/green checks: missing borrowed-session API; missing example files; interrupted pending-count reporting; initializer parameter validation.
|
|
40
|
+
- RSpec covers local PTY native connector ownership, real bounded pool, static/Netdisco inventory, config/env priority, initialization, failure isolation and report persistence.
|
|
41
|
+
- Final commands: `bundle exec rake`, `bundle exec rake build`, `ruby script/verify_package.rb pkg/vxlan-0.2.3.gem`. Logs under /tmp/vxlan-*.log.
|
|
42
|
+
- Core and package runtime dependency checks exclude net-connector/dotenv. Source paths and locally installed library checked after installation.
|
|
43
|
+
- No device networking, configuration writes, Git commit, push or publish performed. Live NX-OS behavior remains unverified.
|
|
44
|
+
|
|
45
|
+
Ruling: published net-connector 0.14.1 still uses concurrency/ and lacks the local resources helper. Host parallel dispatch therefore lives in examples/support/parallel.rb, independent of connector internals. Verify the real published gem separately from the locally refactored installation.
|
|
46
|
+
|
|
47
|
+
Published SDK check: downloaded original net-connector 0.14.1 gem into a separate temporary GEM_HOME; verified loaded path and ran native PTY plus collection/Netdisco specs successfully. Thread-pool regressions cover a blocked sibling on failure and partial thread creation before the startup gate.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Fabric 业务一致性与交互下发实施计划
|
|
2
|
+
|
|
3
|
+
> 使用 executing-plans 在当前工作区逐项执行;保留既有 WIP,不提交、不发布、不实际下发。
|
|
4
|
+
|
|
5
|
+
**目标:** 同 Fabric 内已承载相同 VLAN/VNI 的 Leaf 提供一致网关和访问控制;保留部署范围差异。
|
|
6
|
+
|
|
7
|
+
**架构:** 显式 Fabric 成员策略限定比较域;Fabric::Review 比较完整配置的业务视图,ChangePlan 只渲染有限增量动作。报告默认只诊断,--scripts 显式输出候选与计划;apply 单计划单设备,真实终端明确确认后,读取当前配置、比对指纹、记录持久意图、顺序执行并回读验收。
|
|
8
|
+
|
|
9
|
+
**约束:** Ruby 4、独立 vxlan/wireless 项目;不根据多数无 ACL 删除访问控制;网关跨 VNI 地址冲突必须阻断;不跨 Fabric 借用模板;不复制身份参数;不自动保存或重放远端写入。
|
|
10
|
+
|
|
11
|
+
- [x] Fabric::Review:显式成员;同 VNI 网关/VRF/Anycast/MTU、ACL 挂载及有序内容、同网段跨 VNI 冲突;未部署业务单列覆盖,不列缺失故障。测试不同部署范围、ACL 缺失/同名异内容、少数 ACL 保留、网关迁移冲突、部分证据。
|
|
12
|
+
- [x] ChangePlan:可核验的设备身份、来源哈希、配置指纹、类型化动作、前提、回退与验收。先支持补挂/补建 ACL 与无冲突缺失网关;不自动覆盖冲突规则。
|
|
13
|
+
- [x] 报告/CLI:collect/review 默认诊断,--scripts 可选导出,--fabric-policy 显式比较域;旧报告离线重评,SZX8/SZX9 产出可审阅策略及报告。
|
|
14
|
+
- [x] apply:无 --yes;非终端拒绝;确认文字绑定主机和计划摘要;确认后再连设备;源配置变化、部分回显拒绝写入;每条命令前后落盘,失败/中断标记 uncertain、停止,不重放/自动回退;完整回读核对目标状态。验证取消、过期配置、命令失败、持久化失败和成功路径。
|
|
15
|
+
- [x] Wireless 同步可选脚本导出与交互 apply;模板原有加固建议保留停用/恢复前提,不把 Radio Down 自动变成开启指令。
|
|
16
|
+
- [x] 真实保存报告回放、完整测试与静态检查、打包验证,报告支持范围与未执行事项。
|
|
17
|
+
|
|
18
|
+
审查重点:不同 VLAN 相同子网的迁移、ACL 隐式 deny/顺序、引用存在但定义缺失、确认后被更改的计划、部分下发与报告落盘失败。
|
|
19
|
+
|
|
20
|
+
## 验证记录(2026-10-20)
|
|
21
|
+
|
|
22
|
+
- VXLAN:323 examples / 0 failures;88 个 Ruby 文件静态检查通过;离线示例通过。
|
|
23
|
+
- Wireless:279 examples / 0 failures;91 个 Ruby 文件静态检查通过;离线示例通过。
|
|
24
|
+
- 两个 gem 均已构建并在独立 GEM_HOME 安装验证;review/apply 帮助与离线 CLI 检查通过。
|
|
25
|
+
- 保存批次重评:SZX8/SZX9 共 14 项聚合差异、29 项受支持的增量计划;Wireless 1 项加固计划。计划逐项通过源文件 SHA256、解析事实指纹、动作前提及重新渲染核验;报告权限与注释草稿检查通过。
|
|
26
|
+
- 报告目录:VXLAN 原批次下 reviews/2026-10-20_01-56-53/;Wireless 原批次下 reviews/2026-10-20_01-54-33/。
|
|
27
|
+
- 当前验证使用已保存现场证据及离线设备替身;本轮没有远端配置下发,也不声称真实固件下发验证通过。独立子代理复审因工作区额度失败,已完成本地自查与失败路径测试。
|
|
28
|
+
- apply 的配置指纹覆盖解析器已建模事实,不覆盖未知配置行;每个计划仅单设备单项,下发一项后其他旧计划需重采重评。冲突 ACL 和跨 VNI 网关迁移仍须业务策略决策,未猜测修复。
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# VXLAN 命令行组织与发布设计
|
|
2
|
+
|
|
3
|
+
状态:已实现并通过离线、PTY 与隔离安装验证;取代上一阶段“宿主实现留在 examples/support”的目录选择,保留已经验证的领域与连接器边界。
|
|
4
|
+
|
|
5
|
+
## 用户目标
|
|
6
|
+
|
|
7
|
+
正式实现不放在 examples/support;examples 是优雅、简短的使用示例。后续通过 bin 发布后,用户可以直接使用命令。拒绝重复样板,功能专注、简单、容易集成。
|
|
8
|
+
|
|
9
|
+
本阶段仅组织现有解析/采集能力,提供可安装入口,不新增 VXLAN 配置下发或健康结论。已有未提交内容保留,不连接真实设备、不提交、不上传 RubyGems。
|
|
10
|
+
|
|
11
|
+
## 目录与职责
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
bin/vxlan # 唯一安装入口,转交 CLI,返回退出码
|
|
15
|
+
lib/vxlan/cli.rb # collect/parse 分发、全局帮助及版本
|
|
16
|
+
lib/vxlan/cli/
|
|
17
|
+
collect.rb # 现有 Collection,改为 CLI::Collect
|
|
18
|
+
parse.rb # 现有离线入口
|
|
19
|
+
options.rb # 采集参数/YAML/环境与初始化
|
|
20
|
+
parallel.rb # 宿主有限并发及资源回收
|
|
21
|
+
report.rb # 私有文件与批次报告
|
|
22
|
+
templates/vxlan.yml # 唯一公开配置模板
|
|
23
|
+
templates/env.example # 唯一凭据变量模板
|
|
24
|
+
examples/
|
|
25
|
+
collect.rb # 调用同一 CLI,指定源码示例默认路径
|
|
26
|
+
parse.rb # 调用同一 CLI
|
|
27
|
+
offline.rb # 保留无输入纯解析演示
|
|
28
|
+
README.md # 常用用法
|
|
29
|
+
spec/cli/ # 原宿主测试迁入,按职责命名
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
直接迁移已有五个实现文件,不保留 support 代理文件、重复模板或 VxlanExamples 常量兼容层。NativeConnector 仍作为 collect 实现中的小型内部适配器,不新增层、不复制登录或执行逻辑。lib/vxlan.rb 不加载 CLI;显式 require vxlan/cli 才加载命令入口,分发时再加载对应 collect/parse 实现。
|
|
33
|
+
|
|
34
|
+
CLI 下的 helper 属于应用入口实现,不作为新的领域 API。Ruby 宿主继续调用现有 Collector/collect_session 与 Observation/Snapshot,不要求借用 CLI 或转换结果。
|
|
35
|
+
|
|
36
|
+
## 使用与接口
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
vxlan --help
|
|
40
|
+
vxlan --version
|
|
41
|
+
vxlan collect --init --non-interactive
|
|
42
|
+
vxlan collect --config vxlan.yml --inventory-only
|
|
43
|
+
vxlan collect --config vxlan.yml --dimensions configuration,nve_peers
|
|
44
|
+
vxlan parse --dimension nve_peers --file nve.txt --complete --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
公开分发入口 `Vxlan::CLI.run(argv, env: ENV, out: $stdout, error: $stderr, input: $stdin, ...)` 返回整数,不在库内 exit。bin 只 require 与 exit。示例脚本保留直接运行条件,require 不触发命令。
|
|
48
|
+
|
|
49
|
+
空参数显示全局帮助并返回0;未知子命令/多余位置参数返回2并给出帮助提示。--version使用Vxlan::VERSION。原有 collect/parse 选项、JSON stdout 与 stderr 进度、退出码0/1/2/130保持。帮助、版本、初始化、静态清单预览不访问设备;帮助和离线解析不主动 require net-connector。
|
|
50
|
+
|
|
51
|
+
examples/collect.rb 和 bin/vxlan 调用同一个 Collect,不复制业务处理。注入输出、环境、连接工厂/执行器的现有测试接口保留在 Collect,顶层 CLI 不增加第二套依赖注入框架。
|
|
52
|
+
|
|
53
|
+
## 路径与初始化
|
|
54
|
+
|
|
55
|
+
- 安装入口默认使用启动目录下 vxlan.yml、.env、vxlans/,从不往 gem 安装目录写配置或报告,也不 chdir。
|
|
56
|
+
- CLI显式路径与进程环境中的VXLAN_CONFIG相对启动目录解析;安装入口的YAML相对目录、known_hosts与netdisco_config相对该YAML目录。
|
|
57
|
+
- 源码 examples/collect.rb 显式传入原有默认配置、环境、输出及路径基准,使既有 examples/vxlan.yml、根目录.env、examples/vxlans 与项目根相对路径继续可用。使用清晰的关键字参数,不引入Context/Settings包装类。
|
|
58
|
+
- 默认值只在Options集中合并;模板只定义公开配置结构,init按入口默认路径生成实际配置,不复制模板内容到examples。已有文件排他保留,0600,初始化后退出。
|
|
59
|
+
- 帮助说明实际入口默认值,不再硬编码 ruby examples/collect.rb。源码帮助与安装帮助均有测试。
|
|
60
|
+
|
|
61
|
+
## 依赖与安装
|
|
62
|
+
|
|
63
|
+
本次保留现有可选依赖政策:`vxlan.gemspec` 不依赖 net-connector/dotenv。离线用户 `gem install vxlan`;联网或读取环境文件的用户另外执行 `gem install net-connector -v '~> 0.14.1'` 与 `gem install dotenv -v '~> 3.0'`。缺依赖在设备 IO 前提示安装方式。安装方式问题未收到具体选择,因此本次不改变现有依赖关系,后续如需一次安装联网能力再单独调整。
|
|
64
|
+
|
|
65
|
+
依赖方向仅 vxlan → net-connector,不向连接器加入 VXLAN 业务;纯解析入口不 require 连接器。
|
|
66
|
+
|
|
67
|
+
CLI直接使用的独立标准库gem按实际require声明(如json、optparse、yaml、fileutils、tempfile、time),不借开发依赖的传递安装。Ruby最低版本仍>=4.0.6,连接器最低版本按已验证0.14.1契约,不采用仅本地工作树具备的内部类。
|
|
68
|
+
|
|
69
|
+
## 迁移与验收
|
|
70
|
+
|
|
71
|
+
1. 先固定入口契约回归,再移动现有五个实现文件并改命名空间/require。保留采集、线程回收及报告失败行为,不夹带第二轮功能优化。
|
|
72
|
+
2. 添加CLI分发与bin入口;更新gemspec的bindir、executables和模板allowlist。只安装bin/vxlan,examples不登记为可执行文件。
|
|
73
|
+
3. 参数路径由显式默认值驱动,验证源码/安装两种上下文、外部cwd与只读gem安装目录、--init不覆盖文件、无环境文件/凭据的帮助和预览。
|
|
74
|
+
4. 迁移前248个用例继续通过;测试迁到spec/cli后,不通过examples/support加载产品实现。追加全局帮助/版本/错误子命令、两种入口输出一致、require无执行,以及默认纯解析加载不含net-connector。
|
|
75
|
+
5. 更新中英文README、examples说明、CHANGELOG与独立包验证脚本。保留“核心无连接器依赖”断言,验证可选依赖约定。
|
|
76
|
+
6. 完整rake、构建与隔离安装验证:真实GEM_HOME/bin/vxlan --help/--version/parse;在用户临时目录collect --init和inventory-only;合成PTY经过正式CLI/Collect;记录实际加载路径。所有验证无真实设备IO。
|
|
77
|
+
|
|
78
|
+
迁移完成后删除examples/support及旧模板,保留examples目录中的薄脚本与offline演示。新增任何类或转发层都需有现有职责无法直接表达的证据。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# VXLAN 与设备连接器集成设计
|
|
2
|
+
|
|
3
|
+
状态:用户已确认并授权继续;已按本方案实施,验证记录见同日期实施计划。
|
|
4
|
+
|
|
5
|
+
## 用户约束
|
|
6
|
+
|
|
7
|
+
- net-connector 负责设备登录、会话、脚本执行;vxlan 集成领域业务。
|
|
8
|
+
- 拒绝重复样板代码,模块功能专注领域,简单、容易集成。
|
|
9
|
+
- 先设计,再落实代码。保留两个工作树已有改动,不提交、不发布、不连接真实设备。
|
|
10
|
+
|
|
11
|
+
## 结论与分层
|
|
12
|
+
|
|
13
|
+
直接使用现有接口集成。VXLAN 是运行在 NX-OS 等平台上的网络功能,当前已有 `:cisco_nxos` 连接器;本轮无需在 net-connector 内增加 VXLAN 模块或新设备类型。
|
|
14
|
+
|
|
15
|
+
调用关系:VXLAN 示例/宿主 → `Vxlan::Collector` → `CtxDriver::Reader/Session` → net-connector 原生设备 → SSH/PTY。离线解析不经过连接器。
|
|
16
|
+
|
|
17
|
+
| 所属 | 保留的职责 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| net-connector | 登录、密钥信任、提示符/分页、超时、脚本回执、操作租约、关闭 |
|
|
20
|
+
| ctx_driver | 单命令回执适配、正文/来源指纹、失败隔离、断链后抑制查询 |
|
|
21
|
+
| vxlan/lib | 固定查询与采集顺序、解析、Observation/Snapshot、完整性和配置关联判断 |
|
|
22
|
+
| vxlan/examples | 环境/YAML、清单筛选、连接所有权、并发、进度、报告及退出码 |
|
|
23
|
+
|
|
24
|
+
这些职责在当前源码基本成立。本轮补齐集成可靠性,不重做已完成的脚本功能,不把 examples 的宿主策略提升为核心公共 API。不新增连接器业务门面、通用作业框架或第二套结果模型。
|
|
25
|
+
|
|
26
|
+
## 最小集成接口
|
|
27
|
+
|
|
28
|
+
沿用 `Vxlan::Collector#collect(connector:, target:, fabric_key:, model: nil, dimensions: ...)` 与 `#collect_session(session:, target:, fabric_key:, platform: :cisco_nxos, model: nil, dimensions: ...)`,返回现有 Snapshot;保留现有 platform 参数契约。
|
|
29
|
+
|
|
30
|
+
- 自管连接:现有 `VxlanExamples::NativeConnector#open` 使用 `Net::Connector.open(:cisco_nxos, ...)`,在原生设备 `with_operation(:vxlan)` 中 yield。Collector 可以直接接收 native,由现有 Reader 适配;移除该路径多余的手工 `CtxDriver::Session.new` 包装。
|
|
31
|
+
- 借用连接:宿主负责 `Net::Connector.open` 和 `native.with_operation(:vxlan)`,内部调用 collect_session。方法不新增自动连接、关闭、提权或重连。使用者须按真实设备传递平台,参数默认值不代表设备识别。
|
|
32
|
+
- 不新增 `Vxlan::Connector`、`Net::Connector::Vxlan`、parse 转发门面、第二种回执协议或 installed CLI。未来出现多个真实宿主共享同一能力时,再评估抽取。
|
|
33
|
+
|
|
34
|
+
宿主最小用法:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Net::Connector.open(:cisco_nxos, **access) do |native|
|
|
38
|
+
native.with_operation(:vxlan) do
|
|
39
|
+
Vxlan::Collector.new.collect_session(
|
|
40
|
+
session: native, target: { host: native.host }, fabric_key: "LAB",
|
|
41
|
+
dimensions: [:configuration, :nve_peers]
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
片段说明资源所有权,不声明 open 的返回值能同时交付关闭错误与业务值。批量示例依然走 Collector#collect,利用其已存在的观测保存与关闭失败分类。
|
|
48
|
+
|
|
49
|
+
## 保持的功能
|
|
50
|
+
|
|
51
|
+
保留 collect.rb、parse.rb、offline.rb 及已有参数、环境变量、路径基准和退出码。不新增第二套 YAML,不改变 CLI > YAML > 环境默认值,不迁移目录规则。
|
|
52
|
+
|
|
53
|
+
12 个维度、配置优先查询、返回顺序、未启用功能判断均复用 Collector/Profile;离线 complete:false 默认值不变。完整证据不代表 Fabric 健康。保留静态/Netdisco 清单、IP/CIDR及名称过滤、默认并发4/上限500、SSH strict、显式JSON、中文摘要、私有报告、初始化不覆盖文件等现有功能。
|
|
54
|
+
|
|
55
|
+
## 需要落实的改进
|
|
56
|
+
|
|
57
|
+
### 1. 会话范围与失败保真
|
|
58
|
+
|
|
59
|
+
修改现有 NativeConnector 的 open 块,在一次采集内持有原生操作租约,避免其他线程在两个维度之间插入命令。不给 Collector 的通用会话协议增加连接器特有方法。
|
|
60
|
+
|
|
61
|
+
为 Collector#collect 补回归:连接失败、查询失败、关闭失败、租约退出/日志收尾失败。Collector 已在 connector.open 的 yield 内登记 observations 和 session_finished;仅在复现证明不足时修改 collector.rb。完成的维度不能因收尾失败被删除,collection 维度保持失败码,不能将其判为成功批次。不新增一套 CompletionError/Snapshot 包装。
|
|
62
|
+
|
|
63
|
+
借用入口由宿主管理收尾错误。文档说明:若要求收尾失败仍保留 Snapshot,应在操作块内登记返回值并分别处理异常,不依赖 open 成功返回后才赋值。正文敏感策略复用 ctx_driver/net-connector。
|
|
64
|
+
|
|
65
|
+
### 2. 并发中止与线程退出
|
|
66
|
+
|
|
67
|
+
维持 examples/support/parallel.rb 的有限并发和启动闸门,不依赖连接器工作树中尚未发布的内部调度类。这段宿主调度用于兼容已发布0.14.1,不在本轮把 Parallel::Pool 提升成跨gem公共协议。
|
|
68
|
+
|
|
69
|
+
修正任务内 Thread.exit/kill 绕过 rescue 后,finished 仍可能交付 nil 的情况:只有真正耗尽任务的 worker 才算正常结束;提前退出明确失败并回收全部线程。线程创建到登记、通知、kill/join 必须覆盖取消,次级收尾失败不得覆盖首个错误。不增加重试、取消令牌框架、整批 deadline 或全局线程设置。
|
|
70
|
+
|
|
71
|
+
### 3. 证据登记与报告失败
|
|
72
|
+
|
|
73
|
+
现有 Collection#collect 在写文件成功后才追加 @results;改为先登记已采集的安全结果,再保存。设备 JSON 成功而 TXT 失败时,保留已有 JSON,不重采设备;批次明确持久化失败,不能宣称全部成功。
|
|
74
|
+
|
|
75
|
+
报告错误不作为连接失败处理。复用现有少量方法/状态,必要时增加单一宿主内部 Report::Error,保存安全阶段/公开相对文件名,不携带正文、凭据或原始异常消息;不建立 Batch/Outcome/事件总线。进度“已采集”和“报告已保存”按实际完成点表述。
|
|
76
|
+
|
|
77
|
+
Interrupt 后先等待全部线程回收,再保存摘要;摘要再次失败仍返回130,stderr显示安全报告故障,不能让异常逃出 rescue 覆盖取消事实。已登记结果、已落盘文件与 pending 目标分别反映真实状态。普通报告错误返回2。
|
|
78
|
+
|
|
79
|
+
保持目录0700、文件0600、唯一批次及逐文件原子替换。现有 publish 只有文件 fsync + rename;补同目录同步,区分替换前失败与已替换但持久性未确认。只局部完善 Report#publish,不反向依赖连接器内部 Storage。已有报告根目录检查类型、符号链接及组/其他用户权限;不安全时在连接前拒绝,说明修正方法,不静默修改权限。新父目录同步及文件系统断电保证不在本轮承诺范围。
|
|
80
|
+
|
|
81
|
+
## 依赖与兼容性
|
|
82
|
+
|
|
83
|
+
- net-connector 保持 Ruby >=3.2,运行依赖不新增 vxlan/ctx_driver。
|
|
84
|
+
- vxlan/ctx_driver 当前 Ruby >=4.0.6,不修改最低版本。
|
|
85
|
+
- vxlan 仍仅在 Gemfile examples 组选择 net-connector ~>0.14.1 与 dotenv;核心 gemspec 不加入连接器。
|
|
86
|
+
- 发布版0.14.1和当前工作树同版本并非同一代码,分别验证,记录加载路径/版本。不得只测工作树就声称兼容发布版。
|
|
87
|
+
- 不为少量本地报告逻辑创建新基础gem,也不复制解析、会话执行或跨层报告模型。
|
|
88
|
+
|
|
89
|
+
## 实施顺序与验收
|
|
90
|
+
|
|
91
|
+
设计确认后用 writing-plans 写逐项实施计划;以下是实施顺序,每项先固定失败回归,再最小修改。
|
|
92
|
+
|
|
93
|
+
1. **原生连接贯通**:修改 examples/support/collection.rb 中 NativeConnector;扩展 spec/example_native_connector_spec.rb、spec/collector_spec.rb。本地PTY验证登录、配置优先/顺序、一次租约、关闭、关闭/租约收尾失败保留事实、断链不重放、借用不关闭。若复现证明连接器有通用缺陷,再单独修复,不预设必须改其产品代码。
|
|
94
|
+
2. **并发稳定性**:修改 examples/support/parallel.rb、spec/example_parallel_spec.rb。Queue 同步验证Thread.exit、Thread.kill、部分线程创建失败、中断及阻塞同伴收尾,全员join且不返回伪成功。
|
|
95
|
+
3. **报告与取消**:修改 examples/support/collection.rb、report.rb、spec/example_collection_spec.rb,必要时新增 spec/example_report_spec.rb。验证ENOSPC、JSON成功/TXT失败、目录同步失败、宽权限/链接目录、取消后摘要失败、敏感异常消息、已采集结果与pending不矛盾。
|
|
96
|
+
4. **文档与打包**:更新 examples/README.md、README.md/README-zh.md、CHANGELOG.md及已有离线门禁;必要时更新 script/verify_package.rb。执行 bundle exec rake、bundle exec rake build 和构建包安装验证;分别验证发布连接器与隔离加载的工作树连接器,记录 Gem.loaded_specs 和 loaded paths。若修改 net-connector,额外执行其相关Minitest、Ruby3.2门禁与 script/ci。
|
|
97
|
+
|
|
98
|
+
仅用合成文本、本地PTY、临时目录。离线帮助/示例不接设备。最终准确区分离线/PTY验证与未执行的真实NX-OS现场验证。
|
|
99
|
+
|
|
100
|
+
本方案的具体修复范围与接口取舍经用户确认后实施;公开接口、职责划分和简化原则保持。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 证据契约驱动的优化设计
|
|
2
|
+
|
|
3
|
+
目标:验证原始输入、领域事实、完整性状态及批次报告之间的可信关系,再集中重复规则。范围沿用用户已批准的三个切面:解析可信性、失败可信性、规则唯一性。
|
|
4
|
+
|
|
5
|
+
约束:保留现有 WIP、公开入口和独立 gem 边界;Ruby >= 4.0.6;不提交、不发布、不连接设备;不复制凭据或未经脱敏的私有回显进入仓库。当前目录已有用户要求的连续改动,继续原位实施;临时快照隔离差分和变异运行,不新建分支、不清理工作树。
|
|
6
|
+
|
|
7
|
+
证据:区分历史来源未核实、人工合成、官方资料派生、历史设备保存回显。每个新样本记录命令、平台、来源、转换说明和 SHA-256,预期值直接人工标注,不使用生产辅助方法推导。官方样例不是实机兼容验收;样本不足保持明确未验证。
|
|
8
|
+
|
|
9
|
+
不变量:等价身份不绕过冲突检查;作用域不能串扰;缺少必需证据不能 complete;任一失败不擦除已获取事实;摘要失败不能留下假成功;声明的配置允许值和实际消费保持一致。减少重复知识,不追求减少厂商语法本身。
|
|
10
|
+
|
|
11
|
+
交付:契约矩阵、最小实现改动、独立字面量预期、变形/故障/定向变异证据、差分回放及隔离安装记录。没有解释的行为差异不能通过验收。
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# VXLAN 源码示例
|
|
2
|
+
|
|
3
|
+
正式命令的配置、环境变量、筛选、报告和退出码见 [bin 使用说明](../bin/README.md)。实现和模板统一位于 `lib/vxlan/cli/`,示例只调用同一 CLI,不维护独立采集流程。
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bundle install
|
|
7
|
+
bundle exec ruby examples/collect.rb --help
|
|
8
|
+
bundle exec ruby examples/parse.rb --examples
|
|
9
|
+
bundle exec ruby examples/offline.rb
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| 文件 | 用途 |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `collect.rb` | 只读批量采集入口,显式提供源码示例默认路径 |
|
|
15
|
+
| `parse.rb` | 文件或 stdin 解析入口 |
|
|
16
|
+
| `offline.rb` | 无输入、无网络的合成 OSPF 演示 |
|
|
17
|
+
|
|
18
|
+
`collect.rb` 保留原有约定:配置为 `examples/vxlan.yml`,环境文件为项目根 `.env`,报告为 `examples/vxlans/`;YAML 相对路径基于项目根目录。初始化使用 `--init --non-interactive`,只创建缺失文件并退出。显式 CLI 路径基于启动目录。
|
|
19
|
+
|
|
20
|
+
正式 `bin/vxlan` 使用启动目录默认值,方便源码运行、安装和独立部署;请勿混淆两种入口。也可以在 `examples/` 中运行 `bundle exec ruby collect.rb --help`。示例只在直接执行时运行,require 不触发采集;帮助和离线解析不加载连接器。
|
data/examples/collect.rb
ADDED
data/examples/parse.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
require "ctx_parser/immutable"
|
|
5
|
+
|
|
6
|
+
module Vxlan
|
|
7
|
+
# ACL 的业务身份是有序转发规则与引用对象,序号和备注不改变策略。
|
|
8
|
+
AclPolicy = Data.define(:rules, :address_groups) do
|
|
9
|
+
# 策略与解析输入解除共享,比较和导出都使用同一份不可变事实。
|
|
10
|
+
def initialize(rules:, address_groups:)
|
|
11
|
+
super(rules: CtxParser::Immutable.copy(rules), address_groups: CtxParser::Immutable.copy(address_groups))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# 未采集、非法或未支持的规则保持未知,完整空定义仍可单独表达。
|
|
15
|
+
def self.rules(entries)
|
|
16
|
+
return nil unless entries.is_a?(Array) && entries.all? do |entry|
|
|
17
|
+
entry.is_a?(Hash) && entry["sequence"].is_a?(Integer) && entry["sequence"].between?(1, 4_294_967_295) &&
|
|
18
|
+
%w[permit deny remark].include?(entry["action"]) && entry["expression"].is_a?(String)
|
|
19
|
+
end
|
|
20
|
+
return nil unless entries.map { |entry| entry["sequence"] }.uniq.size == entries.size
|
|
21
|
+
entries.sort_by { |entry| entry.fetch("sequence") }.reject { |entry| entry["action"] == "remark" }.map { |entry| entry.slice("action", "expression") }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 未知端口组或时间范围不能证明策略一致;已解析地址组按成员集合比较。
|
|
25
|
+
def self.resolve(config, entries)
|
|
26
|
+
rules = self.rules(entries)
|
|
27
|
+
return nil unless rules
|
|
28
|
+
return nil if rules.any? { |rule| rule["expression"].match?(/\b(?:portgroup|object-group|time-range)\b/) }
|
|
29
|
+
names = rules.flat_map { |rule| rule["expression"].scan(/\baddrgroup\s+(\S+)/).flatten }.uniq.sort
|
|
30
|
+
groups = names.to_h do |name|
|
|
31
|
+
members = config.fetch("address_groups", {})[name]
|
|
32
|
+
return nil unless members.is_a?(Array) && members.any? && members.all? { |value| value.is_a?(String) && value.include?("/") && IPAddr.new(value).ipv4? }
|
|
33
|
+
[ name, members.uniq.sort ]
|
|
34
|
+
end
|
|
35
|
+
new(rules: rules, address_groups: groups)
|
|
36
|
+
rescue IPAddr::Error
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 新建定义影响本机全部既有引用,审查与动作前后检查共用引用范围。
|
|
41
|
+
def self.bindings(config, name)
|
|
42
|
+
config.fetch("interfaces", {}).flat_map do |interface, values|
|
|
43
|
+
values.fetch("acl_bindings", {}).filter_map { |direction, acl| { "interface" => interface, "direction" => direction } if acl == name }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 仅在报告边界转为普通数据,不把 Ruby 对象的 inspect 当成策略摘要。
|
|
48
|
+
def to_h = { "rules" => rules, "address_groups" => address_groups }.freeze
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vxlan
|
|
4
|
+
class ChangePlan
|
|
5
|
+
# 补齐单个 SVI 的 ACL 保护,固定有序规则与依赖,不覆盖其他策略。
|
|
6
|
+
class SviAcl < ChangePlan
|
|
7
|
+
KIND = "svi_acl"
|
|
8
|
+
FIELDS = [ *ChangePlan::FIELDS, *%w[acl direction entries address_groups create_acl previous_binding] ].freeze
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def validate_parameters!
|
|
13
|
+
super
|
|
14
|
+
name, direction = @change.values_at("acl", "direction")
|
|
15
|
+
raise ArgumentError, "ACL 参数无效" unless name.is_a?(String) && name.match?(/\A[A-Za-z0-9_.-]{1,63}\z/) && %w[in out].include?(direction)
|
|
16
|
+
raise ArgumentError, "ACL 创建状态无效" unless [ true, false ].include?(@change["create_acl"])
|
|
17
|
+
raise ArgumentError, "不能覆盖既有绑定" unless [ nil, name ].include?(@change["previous_binding"])
|
|
18
|
+
groups = @change.fetch("address_groups", {})
|
|
19
|
+
raise ArgumentError, "ACL 依赖未验证" unless groups.is_a?(Hash)
|
|
20
|
+
@policy = AclPolicy.resolve({ "address_groups" => groups }, @change["entries"])
|
|
21
|
+
raise ArgumentError, "ACL 规则或依赖未验证" unless @policy && @policy.address_groups == groups
|
|
22
|
+
raise ArgumentError, "无有效 ACL 规则" if @policy.rules.empty?
|
|
23
|
+
@rule_commands = rule_commands
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 策略已证明序号和结构有效;这里只约束可下发的命令语法,备注不下发。
|
|
27
|
+
def rule_commands
|
|
28
|
+
rules = @change["entries"].sort_by { |entry| entry["sequence"] }.filter_map do |entry|
|
|
29
|
+
next if entry["action"] == "remark"
|
|
30
|
+
action, expression = entry.values_at("action", "expression")
|
|
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 规则语法未验证"
|
|
33
|
+
end
|
|
34
|
+
" #{entry['sequence']} #{action} #{expression}"
|
|
35
|
+
end
|
|
36
|
+
CtxParser::Immutable.copy(rules)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def render
|
|
40
|
+
name, direction = @change.values_at("acl", "direction")
|
|
41
|
+
additions = @change["create_acl"] ? [ "ip access-list #{name}", *@rule_commands, "exit" ] : []
|
|
42
|
+
commands = [ "configure terminal", *additions, "interface #{interface}", " ip access-group #{name} #{direction}", "end" ]
|
|
43
|
+
rollback = [ "configure terminal" ]
|
|
44
|
+
rollback.concat([ "interface #{interface}", " no ip access-group #{name} #{direction}", "exit" ]) unless @change["previous_binding"]
|
|
45
|
+
rollback << "no ip access-list #{name}" if @change["create_acl"]
|
|
46
|
+
[ commands, [ *rollback, "end" ] ]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def eligible_state?(config)
|
|
50
|
+
svi = mapped_svi(config)
|
|
51
|
+
return false unless svi && AclPolicy.resolve(config, @change["entries"]) == @policy
|
|
52
|
+
return false if @change["create_acl"] && !creation_isolated?(config)
|
|
53
|
+
entries = config.fetch("acls", {})[@change["acl"]]
|
|
54
|
+
svi.fetch("acl_bindings", {})[@change["direction"]] == @change["previous_binding"] &&
|
|
55
|
+
(@change["create_acl"] ? entries.nil? : AclPolicy.rules(entries) == @policy.rules)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def target_state?(config)
|
|
59
|
+
return false if @change["create_acl"] && !creation_isolated?(config)
|
|
60
|
+
config.dig("interfaces", interface, "acl_bindings", @change["direction"]) == @change["acl"] &&
|
|
61
|
+
AclPolicy.resolve(config, config.fetch("acls", {})[@change["acl"]]) == @policy
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 新建定义不能顺带激活其他接口或方向的既有引用。
|
|
65
|
+
def creation_isolated?(config)
|
|
66
|
+
target = { "interface" => interface, "direction" => @change["direction"] }
|
|
67
|
+
(AclPolicy.bindings(config, @change["acl"]) - [ target ]).empty?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def remove_changed_fields(context)
|
|
71
|
+
context.dig("interfaces", interface, "acl_bindings")&.delete(@change["direction"])
|
|
72
|
+
context.fetch("acls", {}).delete(@change["acl"]) if @change["create_acl"]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vxlan
|
|
4
|
+
class ChangePlan
|
|
5
|
+
# 仅为空 SVI 补齐同业务网关,不替换已有地址或租户身份。
|
|
6
|
+
class SviGateway < ChangePlan
|
|
7
|
+
KIND = "svi_gateway_add"
|
|
8
|
+
FIELDS = [ *ChangePlan::FIELDS, "vrf", "address" ].freeze
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def validate_parameters!
|
|
13
|
+
super
|
|
14
|
+
validate_gateway!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
[ [ "configure terminal", "interface #{interface}", " ip address #{@change['address']}", "end" ],
|
|
19
|
+
[ "configure terminal", "interface #{interface}", " no ip address #{@change['address']}", "end" ] ]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eligible_state?(config)
|
|
23
|
+
svi = mapped_svi(config)
|
|
24
|
+
svi && gateway_context?(config) && svi.fetch("addresses", []).empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def target_state?(config)
|
|
28
|
+
gateway_context?(config) && config.dig("interfaces", interface, "addresses") == [ @change["address"] ]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def remove_changed_fields(context)
|
|
32
|
+
context.dig("interfaces", interface)&.delete("addresses")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vxlan
|
|
4
|
+
class ChangePlan
|
|
5
|
+
# 只把既有未关联 VLAN 接入已经部署且业务一致的 L2 VNI。
|
|
6
|
+
class VlanVni < ChangePlan
|
|
7
|
+
KIND = "vlan_vni_add"
|
|
8
|
+
FIELDS = [ *ChangePlan::FIELDS, "vrf", "address" ].freeze
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def validate_parameters!
|
|
13
|
+
super
|
|
14
|
+
validate_gateway!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
[ [ "configure terminal", "vlan #{@change['vlan']}", " vn-segment #{@change['vni']}", "end" ],
|
|
19
|
+
[ "configure terminal", "vlan #{@change['vlan']}", " no vn-segment", "end" ] ]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eligible_state?(config)
|
|
23
|
+
mappings = config.fetch("vlan_vnis", {})
|
|
24
|
+
mappings.key?(@change["vlan"].to_s) && mappings[@change["vlan"].to_s].nil? && target_state?(config)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 补关联前后均须保留唯一 VNI、业务网关及完整的 NVE/EVPN L2 承载。
|
|
28
|
+
def target_state?(config)
|
|
29
|
+
member = config.dig("nve", "members", @change["vni"].to_s)
|
|
30
|
+
gateway_context?(config) && config.dig("interfaces", interface, "addresses") == [ @change["address"] ] &&
|
|
31
|
+
member && member["associate_vrf"] == false && config.dig("evpn", @change["vni"].to_s, "type") == "l2" &&
|
|
32
|
+
config.fetch("vlan_vnis", {}).none? { |vlan, vni| vlan != @change["vlan"].to_s && vni == @change["vni"] }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def remove_changed_fields(context)
|
|
36
|
+
context.fetch("vlan_vnis", {}).delete(@change["vlan"].to_s)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|