vxlan 0.2.1 → 0.2.3

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.
@@ -5,18 +5,28 @@ module Vxlan
5
5
  module Syntax
6
6
  # 路由作用域保留邻居、模板、VRF 与地址族的归属。
7
7
  module Routing
8
+ BGP_LEAF = /\A(?:no )?(?:remote-as|inherit peer|update-source|route-map|next-hop-self|route-reflector-client|advertise-pip|advertise l2vpn evpn|send-community|retain route-target)\b/
9
+ private_constant :BGP_LEAF
10
+
8
11
  private
9
12
 
10
13
  # 解析支持的路由作用域及协议关系。
11
- def routing(block, protocol, context = :process)
14
+ def routing(block, protocol, context = :process, family: nil, parent: nil)
12
15
  values = { vrfs: {}, neighbors: {}, templates: {}, address_families: {}, redistribute: [] }
13
16
  analyze_children(block) do |child|
17
+ next issue(child) if protocol != :bgp && BGP_LEAF.match?(child.text)
18
+ next issue(child) unless routing_leaf?(child.text, protocol, context, family, parent)
19
+
14
20
  case child.text
15
21
  when /\Avrf (\S+)\z/ then routing_scope(values, :vrfs, $1, child, protocol, context)
16
22
  when /\Atemplate peer (\S+)\z/ then routing_scope(values, :templates, $1, child, protocol, context)
17
23
  when /\Aneighbor (\S+)\z/ then routing_scope(values, :neighbors, $1, child, protocol, context)
18
24
  when /\Aaddress-family (.+)\z/ then routing_scope(values, :address_families, $1, child, protocol, context)
19
- when /\Arouter-id (\S+)\z/ then assign(values, :router_id, address($1), child)
25
+ when /\Arouter-id (\S+)\z/
26
+ router_id = ip_address($1)
27
+ raise ArgumentError unless IPAddr.new(router_id).ipv4?
28
+
29
+ assign(values, :router_id, router_id, child)
20
30
  when /\Aremote-as (\S+)\z/
21
31
  value = $1
22
32
  integer(value, 1..4_294_967_295) unless %w[internal external].include?(value)
@@ -42,9 +52,12 @@ module Vxlan
42
52
  assign(values, :extended_community, enabled, child) if kind
43
53
  when /\Anetwork (\S+)(?: route-map (\S+))?\z/
44
54
  subnet, route_map = $1, $2
45
- raise ArgumentError unless protocol == :bgp
55
+ unless protocol == :bgp && context == :address_families && %i[process vrfs].include?(parent) &&
56
+ /\Aipv[46] (?:unicast|multicast)\z/.match?(family)
57
+ next issue(child)
58
+ end
46
59
 
47
- (values[:networks] ||= []) << { prefix: network_prefix(subnet), route_map: route_map }.compact
60
+ (values[:networks] ||= []) << { prefix: network_prefix(subnet, family: family.split.first), route_map: route_map }.compact
48
61
  when /\Aredistribute (\S+)(?: (?!route-map\b)(\S+))?(?: route-map (\S+))?\z/
49
62
  values[:redistribute] << { protocol: $1, process: $2, route_map: $3 }.compact
50
63
  when /\Aretain route-target (all|route-map \S+)\z/ then assign(values, :retain_route_target, $1, child)
@@ -56,18 +69,44 @@ module Vxlan
56
69
  values
57
70
  end
58
71
 
72
+ # 已建模叶子必须位于其使用方作用域,不能只因文字可解析就接受事实。
73
+ def routing_leaf?(text, protocol, context, family, parent)
74
+ process_family = context == :address_families && %i[process vrfs].include?(parent)
75
+ case text
76
+ when /\A(?:remote-as|inherit peer|update-source)\b/
77
+ protocol == :bgp && %i[neighbors templates].include?(context)
78
+ when /\Aroute-map\b/
79
+ protocol == :bgp && context == :address_families && %i[neighbors templates].include?(parent)
80
+ when /\Aredistribute\b/
81
+ if protocol == :ospf
82
+ %i[process vrfs].include?(context)
83
+ else
84
+ process_family && /\Aipv[46] (?:unicast|multicast)\z/.match?(family)
85
+ end
86
+ when /\Aretain route-target\b/
87
+ protocol == :bgp && process_family && family == "l2vpn evpn"
88
+ when /\Arouter-id\b/
89
+ %i[process vrfs].include?(context)
90
+ else true
91
+ end
92
+ end
93
+
59
94
  # 只进入当前事实模型可表达的子作用域,阻止任意递归及跨协议误归属。
60
95
  def routing_scope(values, kind, name, block, protocol, context)
61
96
  supported = case kind
62
97
  when :vrfs then context == :process
63
98
  when :templates then protocol == :bgp && context == :process
64
99
  when :neighbors then protocol == :bgp && %i[process vrfs].include?(context)
65
- when :address_families then protocol == :bgp && %i[process vrfs neighbors templates].include?(context)
100
+ when :address_families
101
+ protocol == :bgp && %i[process vrfs neighbors templates].include?(context) &&
102
+ /\A(?:ipv[46] (?:unicast|multicast)|vpnv[46] unicast|l2vpn evpn)\z/.match?(name)
66
103
  end
67
104
  return issue(block) unless supported
68
105
 
69
106
  conflicts_before = @configuration_conflict_count
70
- store(values[kind], name, block, path: @source_path + [ kind, name ]) { routing(block, protocol, kind) }
107
+ store_unique_fact(values[kind], name, block, path: @source_path + [ kind, name ]) do
108
+ routing(block, protocol, kind, family: kind == :address_families ? name : nil, parent: context)
109
+ end
71
110
  # 邻居及模板的子地址族冲突也使继承身份失效;保留同进程其他邻居。
72
111
  if protocol == :bgp && %i[neighbors templates].include?(kind) && @configuration_conflict_count > conflicts_before
73
112
  exclude_conflicted_scope(values[kind], name, block)
@@ -8,7 +8,9 @@ module Vxlan
8
8
  private_constant :INVALID
9
9
 
10
10
  # 检查原始正文,避免 strip 或字段解析先吞掉控制字符。
11
- def self.invalid_output?(text) = INVALID.match?(text)
11
+ def self.invalid_output?(text, allow_cr_newlines: false)
12
+ INVALID.match?(text) || (!allow_cr_newlines && /\r(?!\n)/.match?(text))
13
+ end
12
14
  end
13
15
  end
14
16
  end
@@ -34,7 +34,7 @@ module Vxlan
34
34
  begin
35
35
  connector.open do |session|
36
36
  reader = CtxDriver::Reader.new(session: session, clock: @clock)
37
- if (code = prepare(reader))
37
+ if (code = prepare_terminal(reader))
38
38
  dimensions.each { |name| observations[name] = failure(name, code) }
39
39
  observations[:collection] = failure(:collection, code)
40
40
  next
@@ -45,7 +45,7 @@ module Vxlan
45
45
  observations[name] = Observation.new(name: name, status: :complete, completed_at: @clock.call,
46
46
  issues: [ { code: :vpc_not_configured } ])
47
47
  else
48
- observations[name] = read(reader, name, config, profile)
48
+ observations[name] = collect_observation(reader, name, config, profile)
49
49
  end
50
50
  end
51
51
  session_finished = true
@@ -72,7 +72,7 @@ module Vxlan
72
72
  private
73
73
 
74
74
  # 采集设备回显前,禁用分页并归一化终端宽度。
75
- def prepare(reader)
75
+ def prepare_terminal(reader)
76
76
  PREPARATION_COMMANDS.each do |command|
77
77
  capture = reader.read(command)
78
78
  return capture.failure.fetch(:code) unless capture.success?
@@ -81,7 +81,7 @@ module Vxlan
81
81
  end
82
82
 
83
83
  # 解析失败限定在单条查询内;Reader 负责传输与回执校验。
84
- def read(reader, name, configuration, profile)
84
+ def collect_observation(reader, name, configuration, profile)
85
85
  command = profile.command(name)
86
86
  capture = reader.read(command)
87
87
  metadata = { started_at: capture.started_at, completed_at: capture.completed_at, evidence: capture.evidence }
@@ -15,6 +15,7 @@ module Vxlan
15
15
 
16
16
  nve_members_by_vni = @configuration.fetch(:nve).fetch(:members, {})
17
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
18
19
  evpn_vnis = @configuration.fetch(:evpn)
19
20
  findings = []
20
21
  vlan_mappings_by_vni.each do |vni, entries|
@@ -23,14 +24,24 @@ module Vxlan
23
24
  findings << { code: :vlan_vni_without_nve, vni: vni, vlan_ids: entries.map(&:first).sort }
24
25
  end
25
26
  nve_members_by_vni.each do |vni, settings|
26
- next if settings[:associate_vrf]
27
+ if settings[:associate_vrf]
28
+ findings << { code: :nve_l3_without_vrf, vni: vni } unless vrf_vnis.include?(vni)
29
+ next
30
+ end
27
31
 
28
32
  findings << { code: :nve_l2_without_vlan, vni: vni } unless vlan_mappings_by_vni.key?(vni)
29
33
  if @configuration[:nv_overlay_evpn] && !evpn_vnis.key?(vni)
30
34
  findings << { code: :nve_l2_without_evpn, vni: vni }
31
35
  end
32
36
  end
33
- evpn_vnis.each_key { |vni| findings << { code: :evpn_vni_without_nve, vni: vni } unless nve_members_by_vni.key?(vni) }
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
34
45
  @configuration.fetch(:vrfs).each do |name, settings|
35
46
  next unless settings[:vni]
36
47
  member = nve_members_by_vni[settings[:vni]]
@@ -14,7 +14,7 @@ module Vxlan
14
14
  def call(address_family: "ipv4")
15
15
  raise ArgumentError, "Unsupported address family" unless %w[ipv4 ipv6].include?(address_family)
16
16
 
17
- bindings = route_map_bindings.transform_values(&:uniq)
17
+ consumers_by_route_map = route_map_consumers.transform_values(&:uniq)
18
18
  references = @configuration.fetch(:route_maps).each_with_object({}) do |(name, entries), result|
19
19
  entries.sort_by { |entry| entry.fetch(:sequence) }.each do |entry|
20
20
  matches = entry.fetch(:prefix_list_matches) do
@@ -22,7 +22,7 @@ module Vxlan
22
22
  end
23
23
  matches.select { |match| match[:address_family] == address_family }.each do |match|
24
24
  route_map_reference = { type: "route_map", name: name, sequence: entry.fetch(:sequence), action: entry.fetch(:action),
25
- bindings: bindings.fetch(name, []).dup }
25
+ bindings: consumers_by_route_map.fetch(name, []).dup }
26
26
  route_map_reference[:selector] = match[:selector] unless match[:selector] == "address"
27
27
  match.fetch(:names).uniq.each do |prefix_list|
28
28
  (result[prefix_list] ||= []) << route_map_reference
@@ -37,7 +37,7 @@ module Vxlan
37
37
  private
38
38
 
39
39
  # 关联关系保留协议、VRF、地址族及对等体,避免共享策略被混用。
40
- def route_map_bindings
40
+ def route_map_consumers
41
41
  result = Hash.new { |hash, key| hash[key] = [] }
42
42
  bgp = @configuration.fetch(:bgp)
43
43
  { "default" => bgp }.merge(bgp.fetch(:vrfs, {})).each do |vrf, settings|
data/lib/vxlan/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Vxlan
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.3"
6
6
  end
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.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors
@@ -81,6 +81,7 @@ files:
81
81
  - lib/vxlan.rb
82
82
  - lib/vxlan/bgp_neighbors.rb
83
83
  - lib/vxlan/cisco_nxos/configuration.rb
84
+ - lib/vxlan/cisco_nxos/configuration_state.rb
84
85
  - lib/vxlan/cisco_nxos/document.rb
85
86
  - lib/vxlan/cisco_nxos/evpn_l2route.rb
86
87
  - lib/vxlan/cisco_nxos/parser.rb