vxlan 0.4.2 → 0.5.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 +6 -0
- data/README-zh.md +1 -1
- data/README.md +1 -1
- data/bin/README.md +2 -2
- data/lib/vxlan/change_plan/svi_acl.rb +7 -7
- data/lib/vxlan/change_plan.rb +7 -7
- data/lib/vxlan/cli/apply.rb +28 -28
- 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 +15 -21
data/lib/vxlan/fabric/scope.rb
CHANGED
|
@@ -13,22 +13,22 @@ module Vxlan
|
|
|
13
13
|
# 在入口验证唯一归属;允许单角色为空,但 Fabric 至少有两个成员。
|
|
14
14
|
def initialize(policy)
|
|
15
15
|
unless policy.is_a?(Hash) && policy.keys == [ "fabrics" ] && policy["fabrics"].is_a?(Hash) && policy["fabrics"].any?
|
|
16
|
-
raise ArgumentError, "
|
|
16
|
+
raise ArgumentError, "A fabrics policy is required"
|
|
17
17
|
end
|
|
18
18
|
@groups = policy["fabrics"].to_h do |name, group|
|
|
19
19
|
unless name.is_a?(String) && !name.empty? && group.is_a?(Hash) && (group.keys - ROLES).empty? && group.any?
|
|
20
|
-
raise ArgumentError, "Fabric
|
|
20
|
+
raise ArgumentError, "Fabric requires explicit role member lists"
|
|
21
21
|
end
|
|
22
22
|
roles = ROLES.to_h do |role|
|
|
23
23
|
hosts = group.fetch(role, [])
|
|
24
|
-
raise ArgumentError, "
|
|
25
|
-
hosts.each { |host| raise ArgumentError, "
|
|
24
|
+
raise ArgumentError, "Role members must be address lists" unless hosts.is_a?(Array)
|
|
25
|
+
hosts.each { |host| raise ArgumentError, "Invalid member address" unless host.is_a?(String) && !host.include?("/") && IPAddr.new(host).to_s == host }
|
|
26
26
|
[ role, CtxParser::Immutable.copy(hosts) ]
|
|
27
27
|
end
|
|
28
|
-
raise ArgumentError, "Fabric
|
|
28
|
+
raise ArgumentError, "Fabric requires at least two members" unless roles.values.flatten.size >= 2
|
|
29
29
|
[ name, roles.freeze ]
|
|
30
30
|
end.freeze
|
|
31
|
-
raise ArgumentError, "
|
|
31
|
+
raise ArgumentError, "Members cannot be duplicated or shared across roles or Fabrics" unless hosts.uniq == hosts
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# 扁平成员集合用于覆盖核对,不改变角色内的比较范围。
|
|
@@ -8,13 +8,13 @@ module Vxlan
|
|
|
8
8
|
class Recommendations
|
|
9
9
|
REFERENCE = "https://www.cisco.com/c/en/us/td/docs/dcn/nx-os/nexus9000/103x/configuration/vxlan/cisco-nexus-9000-series-nx-os-vxlan-configuration-guide-release-103x/m_configuring_vxlan_bgp_evpn.html"
|
|
10
10
|
TITLES = {
|
|
11
|
-
"vlan_vni_without_nve" => "VLAN
|
|
12
|
-
"nve_l2_without_vlan" => "L2 NVE
|
|
13
|
-
"nve_l2_without_evpn" => "L2 NVE
|
|
14
|
-
"nve_l3_without_vrf" => "L3 NVE
|
|
15
|
-
"evpn_vni_without_nve" => "EVPN VNI
|
|
16
|
-
"vni_layer_conflict" => "VNI
|
|
17
|
-
"l3_vni_without_nve" => "VRF VNI
|
|
11
|
+
"vlan_vni_without_nve" => "VLAN maps to a VNI but the NVE member is missing",
|
|
12
|
+
"nve_l2_without_vlan" => "L2 NVE member lacks a VLAN mapping",
|
|
13
|
+
"nve_l2_without_evpn" => "L2 NVE member lacks EVPN configuration",
|
|
14
|
+
"nve_l3_without_vrf" => "L3 NVE member lacks a VRF association",
|
|
15
|
+
"evpn_vni_without_nve" => "EVPN VNI lacks an NVE member",
|
|
16
|
+
"vni_layer_conflict" => "VNI has conflicting L2/L3 attributes",
|
|
17
|
+
"l3_vni_without_nve" => "VRF VNI lacks an NVE member"
|
|
18
18
|
}.freeze
|
|
19
19
|
|
|
20
20
|
# 联合证据可以解决本机未知,但角色冲突优先阻断所有草稿;不改变原始发现。
|
|
@@ -58,11 +58,11 @@ module Vxlan
|
|
|
58
58
|
config.fetch("interfaces", {}).flat_map do |name, interface|
|
|
59
59
|
interface.fetch("acl_bindings", {}).filter_map do |direction, acl|
|
|
60
60
|
next if config.fetch("acls", {}).key?(acl)
|
|
61
|
-
result = item("acl_reference_undefined", "#{name} / #{direction}", "
|
|
61
|
+
result = item("acl_reference_undefined", "#{name} / #{direction}", "Interface references ACL #{acl}, but its local definition is missing",
|
|
62
62
|
evidence: { interface: name, direction: direction, acl: acl, addresses: interface.fetch("addresses", []), vlan_vni: config.fetch("vlan_vnis", {})[name.delete_prefix("Vlan")] },
|
|
63
63
|
verify: [ "show running-config", "show ip interface brief vrf all" ])
|
|
64
|
-
result[:blockers] << "
|
|
65
|
-
result[:impact] = "
|
|
64
|
+
result[:blockers] << "First confirm whether the interface carries a service and identify the authoritative ACL definition; missing addresses or VNIs may be residual configuration. Do not infer a service outage or copy another Fabric's ACL by name."
|
|
65
|
+
result[:impact] = "Adding an ACL definition affects all local references to that name; verify every reference and the rollback scope."
|
|
66
66
|
result[:references] = []
|
|
67
67
|
result
|
|
68
68
|
end
|
|
@@ -73,20 +73,20 @@ module Vxlan
|
|
|
73
73
|
def item(code, scope, title, evidence:, verify:, category: "configuration")
|
|
74
74
|
{ code: code, scope: scope, title: title, category: category, status: "blocked", evidence: evidence,
|
|
75
75
|
prerequisites: [], blockers: [], commands: [], rollback: [], verify: verify,
|
|
76
|
-
expected: "
|
|
76
|
+
expected: "Collect again and verify this fact; service connectivity requires separate acceptance.", references: [ REFERENCE ] }
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# 仅在完整配置证实关联缺失时讨论增量补齐,不自动删除疑似闲置配置。
|
|
80
80
|
def configuration_item(config, finding)
|
|
81
81
|
vni = finding["vni"]
|
|
82
|
-
result = item(finding["code"], "VNI #{vni}", TITLES.fetch(finding["code"], "
|
|
82
|
+
result = item(finding["code"], "VNI #{vni}", TITLES.fetch(finding["code"], "Configuration associations require verification"),
|
|
83
83
|
evidence: finding, verify: [ "show running-config", "show nve vni", "show nve peers", "show bgp l2vpn evpn summary" ])
|
|
84
84
|
result[:vni] = vni
|
|
85
|
-
result[:prerequisites] = [ "
|
|
86
|
-
result[:impact] = "
|
|
87
|
-
result[:expected] = "VLAN
|
|
85
|
+
result[:prerequisites] = [ "Confirm that this VNI should carry a service locally, rather than being staged configuration or a decommissioning remnant.", "Check the current software version, device role, and same-Fabric peers' VLAN/VNI, RD/RT and replication mode.", "Collect configuration again and verify it matches this report; after maintenance window approval, operate one device at a time. On anomalies, read back first and avoid blind replay." ]
|
|
86
|
+
result[:impact] = "Completing the configuration may start route advertisement or extend the L2 broadcast domain; verify isolation boundaries, peer configuration and rollback conditions."
|
|
87
|
+
result[:expected] = "VLAN, NVE and EVPN associations are consistent; VNIs expected to carry services are Up, BGP/peers are healthy, and service acceptance passes."
|
|
88
88
|
unless vni.is_a?(Integer) && vni.between?(1, 16_777_215)
|
|
89
|
-
result[:blockers] << "VNI
|
|
89
|
+
result[:blockers] << "VNI is not a valid integer; commands cannot be generated."
|
|
90
90
|
return result
|
|
91
91
|
end
|
|
92
92
|
vlans = config.fetch("vlan_vnis", {}).select { |_vlan, mapped| mapped == vni }.keys
|
|
@@ -97,9 +97,9 @@ module Vxlan
|
|
|
97
97
|
case finding["code"]
|
|
98
98
|
when "nve_l2_without_evpn"
|
|
99
99
|
if l2_context?(config, vni, vlans) && member && member["associate_vrf"] == false && member["replication"] == "bgp" && !evpn && auto_policy?(config)
|
|
100
|
-
candidate(result, evpn_add(vni), evpn_remove(vni), "
|
|
100
|
+
candidate(result, evpn_add(vni), evpn_remove(vni), "All existing local L2 EVPN instances use RD auto and RT import/export auto; the candidate follows this pattern, but the service intent of this VNI still requires confirmation.")
|
|
101
101
|
else
|
|
102
|
-
result[:blockers] << "
|
|
102
|
+
result[:blockers] << "A unique VLAN mapping, L2 BGP replication and consistent RD/RT policy are required; missing or conflicting evidence does not permit applying auto."
|
|
103
103
|
end
|
|
104
104
|
when "vlan_vni_without_nve", "evpn_vni_without_nve"
|
|
105
105
|
if l2_context?(config, vni, vlans) && !member && bgp_replication?(nve) && (evpn ? auto_evpn?(evpn) : auto_policy?(config))
|
|
@@ -107,15 +107,15 @@ module Vxlan
|
|
|
107
107
|
rollback = [ "interface #{nve['interface']}", " no member vni #{vni}", "exit" ]
|
|
108
108
|
commands.concat(evpn_add(vni)) unless evpn
|
|
109
109
|
rollback.concat(evpn_remove(vni)) unless evpn
|
|
110
|
-
candidate(result, commands, rollback, "
|
|
110
|
+
candidate(result, commands, rollback, "Existing local L2 members uniformly use BGP replication and EVPN auto policy; complete only missing NVE/EVPN configuration, without enabling additional ARP suppression.")
|
|
111
111
|
else
|
|
112
|
-
result[:blockers] << "
|
|
112
|
+
result[:blockers] << "Confirm the VNI's L2/L3 role, VLAN, NVE interface, replication mode and RD/RT; a unique configuration change cannot currently be determined."
|
|
113
113
|
end
|
|
114
114
|
when "nve_l2_without_vlan"
|
|
115
|
-
result[:blockers] << "
|
|
115
|
+
result[:blockers] << "Provide the VLAN ID for this VNI and verify access/trunk/SVI configuration; do not infer it from VNI suffixes or other device names."
|
|
116
116
|
result[:template] = [ "vlan <VLAN_ID>", " vn-segment #{vni}" ]
|
|
117
117
|
else
|
|
118
|
-
result[:blockers] << "
|
|
118
|
+
result[:blockers] << "An explicit VRF/VNI service mapping and L2/L3 role are required; verify the design before completing or removing residual configuration."
|
|
119
119
|
end
|
|
120
120
|
result
|
|
121
121
|
end
|
|
@@ -175,8 +175,8 @@ module Vxlan
|
|
|
175
175
|
records = observation.fetch("records", [])
|
|
176
176
|
evidence.merge!(configuration_records: records.size, review_status: records.map { |record| record["review_status"] })
|
|
177
177
|
end
|
|
178
|
-
item("evidence_incomplete", name, "
|
|
179
|
-
verify: Array(CiscoNxos::Profile::COMMANDS[name.to_sym]), category: "evidence").merge(status: "investigate", expected: "
|
|
178
|
+
item("evidence_incomplete", name, "Evidence incomplete; collect missing evidence first", evidence: evidence,
|
|
179
|
+
verify: Array(CiscoNxos::Profile::COMMANDS[name.to_sym]), category: "evidence").merge(status: "investigate", expected: "A complete response or explicit zero-record evidence is required; an empty response does not establish absence of configuration.")
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
|
|
@@ -185,8 +185,8 @@ module Vxlan
|
|
|
185
185
|
%w[nve_vnis nve_peers bgp_neighbors].filter_map do |dimension|
|
|
186
186
|
rows = @dimensions.fetch(dimension, {}).fetch("records", []).select { |row| %w[down idle active connect opensent openconfirm].include?(row["state"].to_s.downcase) }
|
|
187
187
|
next if rows.empty?
|
|
188
|
-
item("#{dimension}_not_up", dimension, "
|
|
189
|
-
verify: [ CiscoNxos::Profile::COMMANDS[dimension.to_sym], "show running-config" ], category: "runtime").merge(status: "investigate", prerequisites: [ "
|
|
188
|
+
item("#{dimension}_not_up", dimension, "Observed runtime state is not established", evidence: rows,
|
|
189
|
+
verify: [ CiscoNxos::Profile::COMMANDS[dimension.to_sym], "show running-config" ], category: "runtime").merge(status: "investigate", prerequisites: [ "Confirm service expectations first, then check peers, underlay routing, interfaces and configuration; do not automatically restart neighbors or enable interfaces." ])
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
end
|
data/lib/vxlan/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gatework contributors
|
|
@@ -13,64 +13,58 @@ dependencies:
|
|
|
13
13
|
name: ctx_cli
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.2.
|
|
18
|
+
version: 0.2.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.2.
|
|
25
|
+
version: 0.2.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: ctx_runner
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.2.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.2.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: ctx_parser
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.2.
|
|
46
|
+
version: 0.2.7
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 0.2.
|
|
53
|
+
version: 0.2.7
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: ctx_driver
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.2'
|
|
61
58
|
- - ">="
|
|
62
59
|
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.2.
|
|
60
|
+
version: 0.2.7
|
|
64
61
|
type: :runtime
|
|
65
62
|
prerelease: false
|
|
66
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
67
64
|
requirements:
|
|
68
|
-
- - "~>"
|
|
69
|
-
- !ruby/object:Gem::Version
|
|
70
|
-
version: '0.2'
|
|
71
65
|
- - ">="
|
|
72
66
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.2.
|
|
67
|
+
version: 0.2.7
|
|
74
68
|
- !ruby/object:Gem::Dependency
|
|
75
69
|
name: digest
|
|
76
70
|
requirement: !ruby/object:Gem::Requirement
|