vxlan 0.2.3 → 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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -0
  3. data/README-zh.md +32 -4
  4. data/README.md +32 -4
  5. data/bin/README.md +203 -0
  6. data/bin/vxlan +8 -0
  7. data/docs/architecture-zh.md +62 -60
  8. data/docs/evidence-contracts.md +54 -0
  9. data/docs/remediation.md +84 -0
  10. data/docs/review-2026-10-19.md +31 -0
  11. data/docs/superpowers/plans/2026-10-19-command-line.md +69 -0
  12. data/docs/superpowers/plans/2026-10-19-connector-integration.md +83 -0
  13. data/docs/superpowers/plans/2026-10-19-evidence-contracts.md +64 -0
  14. data/docs/superpowers/plans/2026-10-19-vxlan-examples.md +47 -0
  15. data/docs/superpowers/plans/2026-10-20-fabric-consistency.md +28 -0
  16. data/docs/superpowers/specs/2026-10-19-command-line-design.md +78 -0
  17. data/docs/superpowers/specs/2026-10-19-connector-integration-design.md +100 -0
  18. data/docs/superpowers/specs/2026-10-19-evidence-contracts.md +11 -0
  19. data/examples/README.md +20 -0
  20. data/examples/collect.rb +8 -0
  21. data/examples/parse.rb +5 -0
  22. data/lib/vxlan/acl_policy.rb +50 -0
  23. data/lib/vxlan/change_plan/svi_acl.rb +76 -0
  24. data/lib/vxlan/change_plan/svi_gateway.rb +36 -0
  25. data/lib/vxlan/change_plan/vlan_vni.rb +40 -0
  26. data/lib/vxlan/change_plan.rb +131 -0
  27. data/lib/vxlan/cisco_nxos/configuration_state.rb +1 -4
  28. data/lib/vxlan/cisco_nxos/evpn_l2route.rb +1 -1
  29. data/lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb +65 -11
  30. data/lib/vxlan/cisco_nxos/parsers/show_cdp_neighbors_detail.rb +32 -8
  31. data/lib/vxlan/cisco_nxos/parsers/show_mac_address_table.rb +1 -1
  32. data/lib/vxlan/cisco_nxos/parsers/show_nve_peers.rb +3 -2
  33. data/lib/vxlan/cisco_nxos/parsers/show_nve_vni.rb +9 -1
  34. data/lib/vxlan/cisco_nxos/parsers/show_running_config.rb +11 -5
  35. data/lib/vxlan/cisco_nxos/parsers/show_vpc_brief.rb +10 -4
  36. data/lib/vxlan/cisco_nxos/syntax/policies.rb +16 -0
  37. data/lib/vxlan/cisco_nxos/syntax/routing.rb +16 -6
  38. data/lib/vxlan/cli/apply.rb +172 -0
  39. data/lib/vxlan/cli/collect.rb +210 -0
  40. data/lib/vxlan/cli/connector.rb +23 -0
  41. data/lib/vxlan/cli/options.rb +311 -0
  42. data/lib/vxlan/cli/parse.rb +58 -0
  43. data/lib/vxlan/cli/report.rb +223 -0
  44. data/lib/vxlan/cli/review/fabric.rb +102 -0
  45. data/lib/vxlan/cli/review/report.rb +165 -0
  46. data/lib/vxlan/cli/review.rb +125 -0
  47. data/lib/vxlan/cli/templates/env.example +12 -0
  48. data/lib/vxlan/cli/templates/vxlan.yml +32 -0
  49. data/lib/vxlan/cli.rb +41 -0
  50. data/lib/vxlan/collector.rb +59 -28
  51. data/lib/vxlan/config/evidence.rb +18 -0
  52. data/lib/vxlan/config/review.rb +56 -0
  53. data/lib/vxlan/{configuration.rb → config.rb} +1 -1
  54. data/lib/vxlan/fabric/review.rb +324 -0
  55. data/lib/vxlan/fabric/scope.rb +43 -0
  56. data/lib/vxlan/observation.rb +3 -0
  57. data/lib/vxlan/recommendations.rb +193 -0
  58. data/lib/vxlan/service/catalog.rb +77 -0
  59. data/lib/vxlan/service/mapping.rb +53 -0
  60. data/lib/vxlan/snapshot.rb +3 -0
  61. data/lib/vxlan/subnet_index.rb +88 -0
  62. data/lib/vxlan/version.rb +1 -1
  63. data/lib/vxlan.rb +4 -2
  64. metadata +137 -7
  65. data/lib/vxlan/cisco_nxos/configuration.rb +0 -10
  66. data/lib/vxlan/configuration_review.rb +0 -54
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Vxlan
6
+ module Service
7
+ # L2 业务由 VLAN、NVE、EVPN 的联合事实识别;缺一个环节仍是已开始部署。
8
+ class Catalog
9
+ # 配置完整性由调用方保证,目录本身不读取设备或推断部署意图。
10
+ def initialize(configs)
11
+ @configs = configs
12
+ end
13
+
14
+ # 一个 VNI 可有多个本地 VLAN 事实,交给审查明确报告歧义。
15
+ def services
16
+ result = Hash.new { |hash, vni| hash[vni] = [] }
17
+ @configs.each do |host, config|
18
+ mappings = config.fetch("vlan_vnis", {})
19
+ nve = config.fetch("nve", {}).fetch("members", {})
20
+ evpn = config.fetch("evpn", {})
21
+ l3 = config.fetch("vrfs", {}).values.filter_map { |vrf| vrf["vni"] }
22
+ l2 = evpn.filter_map { |vni, member| Integer(vni) if member["type"] == "l2" }
23
+ l2.concat(nve.filter_map { |vni, member| Integer(vni) unless member["associate_vrf"] })
24
+ l3.concat(nve.filter_map { |vni, member| Integer(vni) if member["associate_vrf"] })
25
+ l3.concat(evpn.filter_map { |vni, member| Integer(vni) if member["type"] == "l3" })
26
+ vnis = (mappings.values.compact - l3 + l2).uniq
27
+ mappings_by_vni = mappings.group_by { |_vlan, vni| vni }
28
+ vnis.sort.each do |vni|
29
+ vlans = mappings_by_vni.fetch(vni, []).map(&:first)
30
+ (vlans.empty? ? [ nil ] : vlans).each do |vlan|
31
+ member, control = nve[vni.to_s], evpn[vni.to_s]
32
+ missing = []
33
+ missing << "VLAN" unless vlan
34
+ missing << "NVE" unless member
35
+ missing << "NVE L2" if member && member["associate_vrf"]
36
+ missing << "EVPN" if config["nv_overlay_evpn"] && !control
37
+ missing << "EVPN L2" if control && control["type"] != "l2"
38
+ result[vni] << { host: host, config: config, vlan: vlan, svi: vlan && config.fetch("interfaces", {})["Vlan#{vlan}"],
39
+ nve: member, evpn: control, missing: missing }
40
+ end
41
+ end
42
+ end
43
+ result
44
+ end
45
+
46
+ # 成员未指定复制方式时继承全局组播;显式 BGP/成员组播优先。
47
+ def self.nve_member(config, member)
48
+ return nil unless member
49
+ inherited = config.dig("nve", "multicast_group")
50
+ if inherited && !member["replication"] && !member["multicast_group"] && !member["associate_vrf"]
51
+ member.merge("multicast_group" => inherited)
52
+ else
53
+ member
54
+ end
55
+ end
56
+
57
+ # 两字节 AS 的 auto RT 为 AS:VNI;其他 AS 形式保留声明,不猜测版本相关派生行为。
58
+ def self.route_targets(entries, asn:, vni:)
59
+ Array(entries).flat_map do |entry|
60
+ directions = entry["direction"] == "both" ? %w[import export] : [ entry["direction"] ]
61
+ directions.map do |direction|
62
+ target = entry["target"]
63
+ target = asn.is_a?(Integer) && asn.between?(1, 65_535) ? "#{asn}:#{vni}" : [ "auto", asn, vni ] if target == "auto"
64
+ [ entry["address_family"], entry.fetch("evpn", false), direction, target ]
65
+ end
66
+ end.uniq.sort_by { |entry| JSON.generate(entry) }
67
+ end
68
+
69
+ # 租户出口细节可以因 Border 职责不同而不同,此投影只比较 VNI 与导入导出声明。
70
+ def self.tenant(config, name)
71
+ vrf = config.fetch("vrfs", {})[name]
72
+ return nil unless vrf
73
+ { "vrf" => name, "vni" => vrf["vni"], "route_targets" => route_targets(vrf["route_targets"], asn: config.dig("bgp", "asn"), vni: vrf["vni"]) }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../change_plan"
4
+ require_relative "../acl_policy"
5
+ require_relative "catalog"
6
+
7
+ module Vxlan
8
+ module Service
9
+ # 由完整同业务对端证明关联关系,而不是通过 VNI 数字编码猜测 VLAN。
10
+ class Mapping
11
+ # 只接受同 Fabric、同角色业务行,返回已校验的业务增量对象或未知。
12
+ def self.infer(vni, row, peers)
13
+ return nil unless peers.map { |peer| peer[:host] }.uniq.size == peers.size
14
+ donors = peers.reject { |peer| peer[:host] == row[:host] }
15
+ return nil unless donors.any? && donors.all? { |peer| peer[:missing].empty? && peer[:svi] }
16
+ signatures = donors.map { |peer| signature(peer[:config], peer[:svi]) }.uniq
17
+ return nil unless signatures.one? && signatures.first
18
+ expected = signatures.first
19
+ return nil unless transport_matches?(vni, row, donors)
20
+ matches = row[:config].fetch("vlan_vnis", {}).filter_map do |vlan, mapped|
21
+ next unless mapped.nil?
22
+ svi = row[:config].fetch("interfaces", {})["Vlan#{vlan}"]
23
+ vlan if svi && signature(row[:config], svi) == expected
24
+ end
25
+ return nil unless matches.one?
26
+ change = { "kind" => "vlan_vni_add", "vlan" => Integer(matches.first), "vni" => vni, "vrf" => expected["vrf"], "address" => expected["addresses"].first }
27
+ ChangePlan.candidate(change, config: row[:config])
28
+ end
29
+
30
+ # 网关、租户、ACL 及其依赖一起界定业务;缺失 Anycast 身份不能当成一致。
31
+ def self.signature(config, svi)
32
+ return nil unless svi["anycast"] && svi["vrf"] && svi.fetch("addresses", []).one? && config["anycast_mac"]
33
+ policies = svi.fetch("acl_bindings", {}).to_h do |direction, name|
34
+ policy = AclPolicy.resolve(config, config.fetch("acls", {})[name])
35
+ return nil unless policy
36
+ [ direction, policy.to_h ]
37
+ end
38
+ svi.slice("addresses", "vrf", "anycast", "shutdown", "mtu").merge("anycast_mac" => config["anycast_mac"], "policies" => policies)
39
+ end
40
+
41
+ # 本机已有 NVE/EVPN 与对端语义一致才允许连接 VLAN;路由冲突不能靠补绑定解决。
42
+ def self.transport_matches?(vni, row, donors)
43
+ values = [ row, *donors ].map do |peer|
44
+ next nil unless peer[:nve] && peer[:evpn]
45
+ config = peer[:config]
46
+ vrf = peer[:svi]&.dig("vrf") || donors.first[:svi]["vrf"]
47
+ [ Catalog.nve_member(config, peer[:nve]), peer[:evpn]["type"], Catalog.route_targets(peer[:evpn]["route_targets"], asn: config.dig("bgp", "asn"), vni: vni), Catalog.tenant(config, vrf) ]
48
+ end
49
+ values.none?(&:nil?) && values.uniq.one?
50
+ end
51
+ end
52
+ end
53
+ end
@@ -25,5 +25,8 @@ module Vxlan
25
25
  # 展示对象身份与状态,避免泄露原始回显或凭据。
26
26
  def inspect = "#<#{self.class.name} fabric=#{fabric_key.inspect} complete=#{complete?}>"
27
27
  alias to_s inspect
28
+
29
+ # PP 默认展开 Data 成员,显式沿用脱敏展示。
30
+ def pretty_print(printer) = printer.text(inspect)
28
31
  end
29
32
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+ require "ctx_parser/immutable"
5
+
6
+ module Vxlan
7
+ # 单设备 SVI 网段索引;按 VRF 和地址族查重叠,保留接口顺序和重复来源。
8
+ class SubnetIndex
9
+ Entry = Data.define(:interface, :vni, :address, :position, :first, :prefix)
10
+ private_constant :Entry
11
+
12
+ # 仅保存查询所需的独立事实;未解析地址不能作为无重叠证据。
13
+ def initialize(config)
14
+ groups, invalid = {}, {}
15
+ position = 0
16
+ config.fetch("interfaces", {}).each do |name, svi|
17
+ next unless name.match?(/\AVlan\d+\z/)
18
+ vni = config.fetch("vlan_vnis", {})[name.delete_prefix("Vlan")]
19
+ svi.fetch("addresses", []).each do |address|
20
+ fields = CtxParser::Immutable.copy([ name, vni, address ])
21
+ network = parse_network(address)
22
+ entry = Entry.new(*fields, position, network&.to_i, network&.prefix)
23
+ position += 1
24
+ if network
25
+ ((groups[svi["vrf"]] ||= {})[network.ipv4?] ||= []) << entry
26
+ else
27
+ (invalid[svi["vrf"]] ||= []) << entry
28
+ end
29
+ end
30
+ end
31
+ @groups = groups.transform_values do |families|
32
+ families.transform_values do |entries|
33
+ prefixes = entries.group_by(&:prefix).transform_values { |rows| rows.group_by(&:first).transform_values(&:freeze).freeze }
34
+ { ordered: entries.sort_by(&:first).freeze, prefixes: prefixes.freeze }.freeze
35
+ end.freeze
36
+ end.freeze
37
+ @invalid = invalid.transform_values(&:freeze).freeze
38
+ freeze
39
+ end
40
+
41
+ # 同业务 VNI 和明确排除的本机接口不构成冲突;其他匹配范围保留完整地址证据。
42
+ def conflicts(address:, vrf:, vni:, except_interface: nil)
43
+ network = IPAddr.new(address)
44
+ @invalid.fetch(vrf, []).each do |entry|
45
+ IPAddr.new(entry.address) if relevant?(entry, vni, except_interface)
46
+ end
47
+ group = @groups.dig(vrf, network.ipv4?)
48
+ return [] unless group
49
+
50
+ entries = overlapping(group, network).select { |entry| relevant?(entry, vni, except_interface) }
51
+ entries.sort_by!(&:position)
52
+ entries.group_by(&:interface).map do |name, rows|
53
+ { interface: name, vni: rows.first.vni, value: rows.map(&:address) }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # 非法地址只在相关查询中重新拒绝,不能让另一 VRF 的未使用事实改变原有边界。
60
+ def parse_network(address)
61
+ IPAddr.new(address)
62
+ rescue ArgumentError
63
+ nil
64
+ end
65
+
66
+ # 区间起点命中的子网配合有限个祖先前缀,覆盖相等、包含和被包含的网段。
67
+ def overlapping(group, network)
68
+ first, last = network.to_i, network.to_range.last.to_i
69
+ ordered = group.fetch(:ordered)
70
+ left = ordered.bsearch_index { |entry| entry.first >= first } || ordered.size
71
+ right = ordered.bsearch_index { |entry| entry.first > last } || ordered.size
72
+ entries = ordered[left...right]
73
+ width = network.ipv4? ? 32 : 128
74
+ group.fetch(:prefixes).each do |prefix, subnets|
75
+ next unless prefix < network.prefix
76
+ shift = width - prefix
77
+ ancestor = (first >> shift) << shift
78
+ entries.concat(subnets.fetch(ancestor, [])) unless ancestor == first
79
+ end
80
+ entries
81
+ end
82
+
83
+ # 相同 VNI 与排除接口的事实保留在索引中,只在本次查询中排除。
84
+ def relevant?(entry, vni, except_interface)
85
+ entry.vni != vni && entry.interface != except_interface
86
+ end
87
+ end
88
+ end
data/lib/vxlan/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Vxlan
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.2.3"
5
+ VERSION = "0.3.0"
6
6
  end
data/lib/vxlan.rb CHANGED
@@ -7,9 +7,11 @@ end
7
7
 
8
8
  require_relative "vxlan/observation"
9
9
  require_relative "vxlan/snapshot"
10
- require_relative "vxlan/configuration"
10
+ require_relative "vxlan/config"
11
11
  require_relative "vxlan/runtime_parser"
12
12
  require_relative "vxlan/bgp_neighbors"
13
- require_relative "vxlan/configuration_review"
13
+ require_relative "vxlan/config/review"
14
14
  require_relative "vxlan/prefix_list_references"
15
15
  require_relative "vxlan/collector"
16
+ require_relative "vxlan/recommendations"
17
+ require_relative "vxlan/fabric/review"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vxlan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors
@@ -29,14 +29,20 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.2.0
32
+ version: '0.2'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 0.2.4
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
36
39
  requirements:
37
40
  - - "~>"
38
41
  - !ruby/object:Gem::Version
39
- version: 0.2.0
42
+ version: '0.2'
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.2.4
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: digest
42
48
  requirement: !ruby/object:Gem::Requirement
@@ -65,9 +71,94 @@ dependencies:
65
71
  - - ">="
66
72
  - !ruby/object:Gem::Version
67
73
  version: '0'
74
+ - !ruby/object:Gem::Dependency
75
+ name: json
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ type: :runtime
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ - !ruby/object:Gem::Dependency
89
+ name: optparse
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ type: :runtime
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ - !ruby/object:Gem::Dependency
103
+ name: yaml
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ - !ruby/object:Gem::Dependency
117
+ name: fileutils
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ - !ruby/object:Gem::Dependency
131
+ name: tempfile
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ type: :runtime
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ - !ruby/object:Gem::Dependency
145
+ name: time
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ type: :runtime
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
68
158
  description: Independent configuration and runtime parsers for VXLAN fabrics, with
69
159
  immutable snapshots and injected collection sessions.
70
- executables: []
160
+ executables:
161
+ - vxlan
71
162
  extensions: []
72
163
  extra_rdoc_files: []
73
164
  files:
@@ -75,12 +166,32 @@ files:
75
166
  - LICENSE
76
167
  - README-zh.md
77
168
  - README.md
169
+ - bin/README.md
170
+ - bin/vxlan
78
171
  - docs/architecture-zh.md
172
+ - docs/evidence-contracts.md
79
173
  - docs/release.md
174
+ - docs/remediation.md
175
+ - docs/review-2026-10-19.md
176
+ - docs/superpowers/plans/2026-10-19-command-line.md
177
+ - docs/superpowers/plans/2026-10-19-connector-integration.md
178
+ - docs/superpowers/plans/2026-10-19-evidence-contracts.md
179
+ - docs/superpowers/plans/2026-10-19-vxlan-examples.md
180
+ - docs/superpowers/plans/2026-10-20-fabric-consistency.md
181
+ - docs/superpowers/specs/2026-10-19-command-line-design.md
182
+ - docs/superpowers/specs/2026-10-19-connector-integration-design.md
183
+ - docs/superpowers/specs/2026-10-19-evidence-contracts.md
184
+ - examples/README.md
185
+ - examples/collect.rb
80
186
  - examples/offline.rb
187
+ - examples/parse.rb
81
188
  - lib/vxlan.rb
189
+ - lib/vxlan/acl_policy.rb
82
190
  - lib/vxlan/bgp_neighbors.rb
83
- - lib/vxlan/cisco_nxos/configuration.rb
191
+ - lib/vxlan/change_plan.rb
192
+ - lib/vxlan/change_plan/svi_acl.rb
193
+ - lib/vxlan/change_plan/svi_gateway.rb
194
+ - lib/vxlan/change_plan/vlan_vni.rb
84
195
  - lib/vxlan/cisco_nxos/configuration_state.rb
85
196
  - lib/vxlan/cisco_nxos/document.rb
86
197
  - lib/vxlan/cisco_nxos/evpn_l2route.rb
@@ -102,14 +213,33 @@ files:
102
213
  - lib/vxlan/cisco_nxos/syntax/policies.rb
103
214
  - lib/vxlan/cisco_nxos/syntax/routing.rb
104
215
  - lib/vxlan/cisco_nxos/terminal.rb
216
+ - lib/vxlan/cli.rb
217
+ - lib/vxlan/cli/apply.rb
218
+ - lib/vxlan/cli/collect.rb
219
+ - lib/vxlan/cli/connector.rb
220
+ - lib/vxlan/cli/options.rb
221
+ - lib/vxlan/cli/parse.rb
222
+ - lib/vxlan/cli/report.rb
223
+ - lib/vxlan/cli/review.rb
224
+ - lib/vxlan/cli/review/fabric.rb
225
+ - lib/vxlan/cli/review/report.rb
226
+ - lib/vxlan/cli/templates/env.example
227
+ - lib/vxlan/cli/templates/vxlan.yml
105
228
  - lib/vxlan/collector.rb
106
- - lib/vxlan/configuration.rb
107
- - lib/vxlan/configuration_review.rb
229
+ - lib/vxlan/config.rb
230
+ - lib/vxlan/config/evidence.rb
231
+ - lib/vxlan/config/review.rb
232
+ - lib/vxlan/fabric/review.rb
233
+ - lib/vxlan/fabric/scope.rb
108
234
  - lib/vxlan/observation.rb
109
235
  - lib/vxlan/parser_registry.rb
110
236
  - lib/vxlan/prefix_list_references.rb
237
+ - lib/vxlan/recommendations.rb
111
238
  - lib/vxlan/runtime_parser.rb
239
+ - lib/vxlan/service/catalog.rb
240
+ - lib/vxlan/service/mapping.rb
112
241
  - lib/vxlan/snapshot.rb
242
+ - lib/vxlan/subnet_index.rb
113
243
  - lib/vxlan/version.rb
114
244
  homepage: https://github.com/gatework/vxlan
115
245
  licenses:
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "configuration_state"
4
-
5
- module Vxlan
6
- module CiscoNxos
7
- # 兼容既有加载路径;配置解析状态由 ConfigurationState 表达。
8
- Configuration = ConfigurationState
9
- end
10
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vxlan
4
- # 配置关联审查独立于语法解析及运行健康检查。
5
- class ConfigurationReview
6
- # 绑定归一化配置,用于交叉引用一致性检查。
7
- def initialize(configuration)
8
- @configuration = configuration
9
- end
10
-
11
- # 检查完整配置中的关联缺失或引用矛盾。
12
- def call(complete: false)
13
- raise ArgumentError, "Completeness must be boolean" unless [ true, false ].include?(complete)
14
- return [] unless complete
15
-
16
- nve_members_by_vni = @configuration.fetch(:nve).fetch(:members, {})
17
- vlan_mappings_by_vni = @configuration.fetch(:vlan_vnis).group_by { |_vlan, vni| vni }
18
- vrf_vnis = @configuration.fetch(:vrfs).values.filter_map { |settings| settings[:vni] }.to_set
19
- evpn_vnis = @configuration.fetch(:evpn)
20
- findings = []
21
- vlan_mappings_by_vni.each do |vni, entries|
22
- next unless vni && !nve_members_by_vni.key?(vni)
23
-
24
- findings << { code: :vlan_vni_without_nve, vni: vni, vlan_ids: entries.map(&:first).sort }
25
- end
26
- nve_members_by_vni.each do |vni, settings|
27
- if settings[:associate_vrf]
28
- findings << { code: :nve_l3_without_vrf, vni: vni } unless vrf_vnis.include?(vni)
29
- next
30
- end
31
-
32
- findings << { code: :nve_l2_without_vlan, vni: vni } unless vlan_mappings_by_vni.key?(vni)
33
- if @configuration[:nv_overlay_evpn] && !evpn_vnis.key?(vni)
34
- findings << { code: :nve_l2_without_evpn, vni: vni }
35
- end
36
- end
37
- evpn_vnis.each do |vni, settings|
38
- member = nve_members_by_vni[vni]
39
- if !member
40
- findings << { code: :evpn_vni_without_nve, vni: vni }
41
- elsif (settings[:type] == "l3") != member[:associate_vrf]
42
- findings << { code: :vni_layer_conflict, vni: vni, evpn_type: settings[:type] }
43
- end
44
- end
45
- @configuration.fetch(:vrfs).each do |name, settings|
46
- next unless settings[:vni]
47
- member = nve_members_by_vni[settings[:vni]]
48
- code = member ? (!member[:associate_vrf] ? :vni_layer_conflict : nil) : :l3_vni_without_nve
49
- findings << { code: code, vni: settings[:vni], vrf: name } if code
50
- end
51
- findings.sort_by { |finding| [ finding[:vni], finding[:code].to_s ] }
52
- end
53
- end
54
- end