netstack 0.1.1 → 0.2.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -3
  3. data/{LICENSE.txt → LICENSE} +7 -6
  4. data/NOTICE +25 -0
  5. data/README.md +58 -20
  6. data/docs/acceptance.md +92 -0
  7. data/docs/backups.md +100 -0
  8. data/docs/configuration.md +198 -0
  9. data/docs/connector.md +187 -0
  10. data/docs/ddi.md +142 -0
  11. data/docs/deployment.md +47 -0
  12. data/docs/foundations.md +45 -0
  13. data/docs/implementation.md +68 -0
  14. data/docs/inventory.md +42 -0
  15. data/docs/monitoring.md +227 -0
  16. data/docs/motor_integration.md +96 -0
  17. data/docs/performance.md +39 -0
  18. data/docs/release.md +20 -0
  19. data/docs/vxlan.md +91 -0
  20. data/docs/wireless.md +81 -0
  21. data/lib/netstack/backups/artifact.rb +23 -0
  22. data/lib/netstack/backups/diff.rb +15 -0
  23. data/lib/netstack/backups/repository.rb +77 -0
  24. data/lib/netstack/backups/request.rb +78 -0
  25. data/lib/netstack/circuits/business_calendar.rb +51 -0
  26. data/lib/netstack/circuits/capacity_review.rb +191 -0
  27. data/lib/netstack/circuits/endpoint.rb +25 -0
  28. data/lib/netstack/circuits/monitoring_plan.rb +212 -0
  29. data/lib/netstack/configuration/artifact.rb +85 -0
  30. data/lib/netstack/configuration/artifact_resolver.rb +30 -0
  31. data/lib/netstack/configuration/bootstrap/payload.rb +67 -0
  32. data/lib/netstack/configuration/bootstrap/validator.rb +224 -0
  33. data/lib/netstack/configuration/catalog.rb +60 -0
  34. data/lib/netstack/configuration/generator.rb +87 -0
  35. data/lib/netstack/configuration/input_contract.rb +82 -0
  36. data/lib/netstack/configuration/payload.rb +168 -0
  37. data/lib/netstack/configuration/reader.rb +124 -0
  38. data/lib/netstack/configuration/support.rb +30 -0
  39. data/lib/netstack/configuration/system_configuration.rb +56 -0
  40. data/lib/netstack/configuration/template_manager.rb +174 -0
  41. data/lib/netstack/configuration/template_store.rb +40 -0
  42. data/lib/netstack/configuration/validator.rb +566 -0
  43. data/lib/netstack/connector/authentication.rb +77 -0
  44. data/lib/netstack/connector/authentication_error.rb +11 -0
  45. data/lib/netstack/connector/backup.rb +19 -0
  46. data/lib/netstack/connector/backup_busy.rb +11 -0
  47. data/lib/netstack/connector/backup_persistence_error.rb +32 -0
  48. data/lib/netstack/connector/command.rb +63 -0
  49. data/lib/netstack/connector/command_result.rb +24 -0
  50. data/lib/netstack/connector/command_timeout.rb +11 -0
  51. data/lib/netstack/connector/configuration.rb +138 -0
  52. data/lib/netstack/connector/connection_closed.rb +11 -0
  53. data/lib/netstack/connector/connection_error.rb +11 -0
  54. data/lib/netstack/connector/device.rb +284 -0
  55. data/lib/netstack/connector/device_error.rb +11 -0
  56. data/lib/netstack/connector/dialogue.rb +55 -0
  57. data/lib/netstack/connector/error.rb +44 -0
  58. data/lib/netstack/connector/error_metadata.rb +58 -0
  59. data/lib/netstack/connector/event.rb +24 -0
  60. data/lib/netstack/connector/execution.rb +110 -0
  61. data/lib/netstack/connector/interaction.rb +55 -0
  62. data/lib/netstack/connector/internal_error.rb +11 -0
  63. data/lib/netstack/connector/known_hosts.rb +128 -0
  64. data/lib/netstack/connector/local_backup.rb +63 -0
  65. data/lib/netstack/connector/log/event.rb +65 -0
  66. data/lib/netstack/connector/log/formatter.rb +18 -0
  67. data/lib/netstack/connector/log/messages.rb +54 -0
  68. data/lib/netstack/connector/log/redacting_writer.rb +33 -0
  69. data/lib/netstack/connector/log/transcript.rb +35 -0
  70. data/lib/netstack/connector/log.rb +262 -0
  71. data/lib/netstack/connector/log_error.rb +11 -0
  72. data/lib/netstack/connector/login_timeout.rb +11 -0
  73. data/lib/netstack/connector/output_limit_exceeded.rb +11 -0
  74. data/lib/netstack/connector/parsing_error.rb +11 -0
  75. data/lib/netstack/connector/profile/builder.rb +238 -0
  76. data/lib/netstack/connector/profile.rb +225 -0
  77. data/lib/netstack/connector/prompt_error.rb +11 -0
  78. data/lib/netstack/connector/recovery.rb +59 -0
  79. data/lib/netstack/connector/redactor.rb +100 -0
  80. data/lib/netstack/connector/response.rb +25 -0
  81. data/lib/netstack/connector/response_reader.rb +140 -0
  82. data/lib/netstack/connector/result.rb +50 -0
  83. data/lib/netstack/connector/running_config/rendered.rb +16 -0
  84. data/lib/netstack/connector/running_config/strategy.rb +28 -0
  85. data/lib/netstack/connector/running_config.rb +113 -0
  86. data/lib/netstack/connector/save_config.rb +23 -0
  87. data/lib/netstack/connector/script.rb +59 -0
  88. data/lib/netstack/connector/script_error.rb +11 -0
  89. data/lib/netstack/connector/script_output_limit_exceeded.rb +11 -0
  90. data/lib/netstack/connector/session.rb +414 -0
  91. data/lib/netstack/connector/session_busy.rb +11 -0
  92. data/lib/netstack/connector/storage/backup_lock.rb +117 -0
  93. data/lib/netstack/connector/storage/private_file.rb +111 -0
  94. data/lib/netstack/connector/storage/safe_file.rb +64 -0
  95. data/lib/netstack/connector/storage/saved_config.rb +76 -0
  96. data/lib/netstack/connector/terminal_renderer.rb +186 -0
  97. data/lib/netstack/connector/terminal_text.rb +32 -0
  98. data/lib/netstack/connector/textfsm.rb +71 -0
  99. data/lib/netstack/connector/tftp/file_upload.rb +53 -0
  100. data/lib/netstack/connector/tftp/strategy.rb +67 -0
  101. data/lib/netstack/connector/tftp.rb +116 -0
  102. data/lib/netstack/connector/tftp_completion_error.rb +36 -0
  103. data/lib/netstack/connector/tftp_receipt.rb +54 -0
  104. data/lib/netstack/connector/tftp_target.rb +65 -0
  105. data/lib/netstack/connector/topology/checkpoint_error.rb +16 -0
  106. data/lib/netstack/connector/topology/deployment_adapter.rb +163 -0
  107. data/lib/netstack/connector/topology/execution_result.rb +34 -0
  108. data/lib/netstack/connector/topology/immediate_strategy.rb +39 -0
  109. data/lib/netstack/connector/topology/interface_description.rb +46 -0
  110. data/lib/netstack/connector/topology/interface_name.rb +60 -0
  111. data/lib/netstack/connector/topology/plan.rb +105 -0
  112. data/lib/netstack/connector/topology/strategy.rb +76 -0
  113. data/lib/netstack/connector/topology.rb +255 -0
  114. data/lib/netstack/connector/transport_error.rb +11 -0
  115. data/lib/netstack/connector/transports.rb +201 -0
  116. data/lib/netstack/connector/underlying_error.rb +25 -0
  117. data/lib/netstack/connector/unsupported_operation.rb +11 -0
  118. data/lib/netstack/connector/vendors/cisco_ios/running_config.rb +22 -0
  119. data/lib/netstack/connector/vendors/cisco_ios/tftp_backup.rb +41 -0
  120. data/lib/netstack/connector/vendors/cisco_ios/topology.rb +54 -0
  121. data/lib/netstack/connector/vendors/cisco_ios.rb +44 -0
  122. data/lib/netstack/connector/vendors/cisco_nxos/running_config.rb +25 -0
  123. data/lib/netstack/connector/vendors/cisco_nxos/tftp_backup.rb +43 -0
  124. data/lib/netstack/connector/vendors/cisco_nxos.rb +51 -0
  125. data/lib/netstack/connector/vendors/h3c/tftp_backup.rb +34 -0
  126. data/lib/netstack/connector/vendors/h3c/topology.rb +72 -0
  127. data/lib/netstack/connector/vendors/h3c.rb +75 -0
  128. data/lib/netstack/connector/vendors/h3c_wireless.rb +13 -0
  129. data/lib/netstack/connector/vendors/hillstone/running_config.rb +16 -0
  130. data/lib/netstack/connector/vendors/hillstone/tftp_backup.rb +55 -0
  131. data/lib/netstack/connector/vendors/hillstone/topology.rb +64 -0
  132. data/lib/netstack/connector/vendors/hillstone.rb +39 -0
  133. data/lib/netstack/connector/vendors/huawei/tftp_backup.rb +26 -0
  134. data/lib/netstack/connector/vendors/huawei.rb +62 -0
  135. data/lib/netstack/connector/vendors/palo_alto/running_config.rb +77 -0
  136. data/lib/netstack/connector/vendors/palo_alto/tftp_backup.rb +49 -0
  137. data/lib/netstack/connector/vendors/palo_alto/topology.rb +77 -0
  138. data/lib/netstack/connector/vendors/palo_alto.rb +39 -0
  139. data/lib/netstack/connector/vendors/radware/running_config.rb +21 -0
  140. data/lib/netstack/connector/vendors/radware/tftp_backup.rb +49 -0
  141. data/lib/netstack/connector/vendors/radware/topology.rb +19 -0
  142. data/lib/netstack/connector/vendors/radware.rb +59 -0
  143. data/lib/netstack/connector/write_timeout.rb +11 -0
  144. data/lib/netstack/connector.rb +32 -0
  145. data/lib/netstack/ddi/applier.rb +107 -0
  146. data/lib/netstack/ddi/deployment.rb +137 -0
  147. data/lib/netstack/ddi/inputs.rb +61 -0
  148. data/lib/netstack/ddi/manager.rb +197 -0
  149. data/lib/netstack/ddi/plan.rb +99 -0
  150. data/lib/netstack/ddi/scope.rb +127 -0
  151. data/lib/netstack/ddi/settings.rb +44 -0
  152. data/lib/netstack/ddi/state.rb +166 -0
  153. data/lib/netstack/deployment/checkpoint_error.rb +14 -0
  154. data/lib/netstack/deployment/cli_adapter.rb +64 -0
  155. data/lib/netstack/deployment/executor.rb +131 -0
  156. data/lib/netstack/deployment/plan.rb +82 -0
  157. data/lib/netstack/deployment/receipt.rb +78 -0
  158. data/lib/netstack/deployment/recovery.rb +19 -0
  159. data/lib/netstack/deployment/step.rb +16 -0
  160. data/lib/netstack/dimension.rb +30 -0
  161. data/lib/netstack/error.rb +14 -0
  162. data/lib/netstack/input_error.rb +9 -0
  163. data/lib/netstack/integrations/bluecat/client.rb +84 -0
  164. data/lib/netstack/integrations/http/client.rb +146 -0
  165. data/lib/netstack/integrations/http/error.rb +10 -0
  166. data/lib/netstack/integrations/http/response.rb +28 -0
  167. data/lib/netstack/integrations/http_delivery/adapter.rb +51 -0
  168. data/lib/netstack/integrations/http_delivery/client.rb +127 -0
  169. data/lib/netstack/integrations/http_delivery/result.rb +27 -0
  170. data/lib/netstack/integrations/oxidized/client.rb +89 -0
  171. data/lib/netstack/integrations/zabbix/client.rb +211 -0
  172. data/lib/netstack/integrations/zabbix/error.rb +10 -0
  173. data/lib/netstack/inventory/address.rb +30 -0
  174. data/lib/netstack/inventory/changes.rb +13 -0
  175. data/lib/netstack/inventory/cli_parser.rb +75 -0
  176. data/lib/netstack/inventory/collector.rb +99 -0
  177. data/lib/netstack/inventory/device.rb +14 -0
  178. data/lib/netstack/inventory/interface.rb +25 -0
  179. data/lib/netstack/inventory/neighbor.rb +15 -0
  180. data/lib/netstack/inventory/reconciler.rb +74 -0
  181. data/lib/netstack/inventory/snapshot.rb +69 -0
  182. data/lib/netstack/inventory/transceiver.rb +23 -0
  183. data/lib/netstack/ipv4.rb +204 -0
  184. data/lib/netstack/ipv6.rb +88 -0
  185. data/lib/netstack/monitoring/applier.rb +179 -0
  186. data/lib/netstack/monitoring/device_plan.rb +23 -0
  187. data/lib/netstack/monitoring/host_plan.rb +152 -0
  188. data/lib/netstack/monitoring/inputs.rb +73 -0
  189. data/lib/netstack/monitoring/measurements.rb +89 -0
  190. data/lib/netstack/monitoring/mutation.rb +37 -0
  191. data/lib/netstack/monitoring/plan.rb +69 -0
  192. data/lib/netstack/monitoring/probe_plan.rb +19 -0
  193. data/lib/netstack/monitoring/state_comparison.rb +37 -0
  194. data/lib/netstack/range_set.rb +181 -0
  195. data/lib/netstack/routing/lookup.rb +46 -0
  196. data/lib/netstack/routing/lookup_result.rb +14 -0
  197. data/lib/netstack/routing/route.rb +32 -0
  198. data/lib/netstack/routing/snapshot.rb +23 -0
  199. data/lib/netstack/target.rb +15 -0
  200. data/lib/netstack/unsupported_capability.rb +9 -0
  201. data/lib/netstack/values.rb +70 -0
  202. data/lib/netstack/version.rb +1 -1
  203. data/lib/netstack/vxlan/auditor.rb +187 -0
  204. data/lib/netstack/vxlan/cisco_nxos/config_parser.rb +327 -0
  205. data/lib/netstack/vxlan/cisco_nxos/deployment_adapter.rb +63 -0
  206. data/lib/netstack/vxlan/cisco_nxos/runtime_parser.rb +182 -0
  207. data/lib/netstack/vxlan/cisco_nxos/segment_renderer.rb +42 -0
  208. data/lib/netstack/vxlan/collector.rb +77 -0
  209. data/lib/netstack/vxlan/fabric.rb +29 -0
  210. data/lib/netstack/vxlan/finding.rb +20 -0
  211. data/lib/netstack/vxlan/node.rb +51 -0
  212. data/lib/netstack/vxlan/route_policy.rb +57 -0
  213. data/lib/netstack/vxlan/runtime_result.rb +24 -0
  214. data/lib/netstack/vxlan/segment.rb +35 -0
  215. data/lib/netstack/vxlan/segment_plan.rb +25 -0
  216. data/lib/netstack/vxlan/segment_planner.rb +156 -0
  217. data/lib/netstack/vxlan/snapshot.rb +34 -0
  218. data/lib/netstack/vxlan/validation.rb +58 -0
  219. data/lib/netstack/vxlan/verifier.rb +52 -0
  220. data/lib/netstack/vxlan/vpc_domain.rb +20 -0
  221. data/lib/netstack/wireless/access_point.rb +12 -0
  222. data/lib/netstack/wireless/ap_group.rb +12 -0
  223. data/lib/netstack/wireless/assessment.rb +13 -0
  224. data/lib/netstack/wireless/assessor.rb +83 -0
  225. data/lib/netstack/wireless/authentication_connection.rb +12 -0
  226. data/lib/netstack/wireless/bss.rb +12 -0
  227. data/lib/netstack/wireless/client.rb +12 -0
  228. data/lib/netstack/wireless/collector.rb +191 -0
  229. data/lib/netstack/wireless/configuration.rb +13 -0
  230. data/lib/netstack/wireless/finding.rb +12 -0
  231. data/lib/netstack/wireless/h3c/config_parser.rb +229 -0
  232. data/lib/netstack/wireless/h3c/lldp_parser.rb +69 -0
  233. data/lib/netstack/wireless/h3c/runtime_parser.rb +240 -0
  234. data/lib/netstack/wireless/huawei/runtime_parser.rb +83 -0
  235. data/lib/netstack/wireless/invalid_snapshot.rb +11 -0
  236. data/lib/netstack/wireless/neighbor.rb +12 -0
  237. data/lib/netstack/wireless/normalizer.rb +90 -0
  238. data/lib/netstack/wireless/parse_result.rb +13 -0
  239. data/lib/netstack/wireless/radio.rb +12 -0
  240. data/lib/netstack/wireless/reconciler.rb +115 -0
  241. data/lib/netstack/wireless/reconciliation.rb +11 -0
  242. data/lib/netstack/wireless/security_policy.rb +12 -0
  243. data/lib/netstack/wireless/service_template.rb +12 -0
  244. data/lib/netstack/wireless/service_template_bindings.rb +56 -0
  245. data/lib/netstack/wireless/snapshot.rb +150 -0
  246. data/lib/netstack/wireless/state.rb +23 -0
  247. data/lib/netstack/wireless/view.rb +154 -0
  248. data/lib/netstack.rb +10 -3
  249. data/resources/configuration/cisco-bootstrap.erb +118 -0
  250. data/resources/configuration/cisco-distributed-gateway.erb +40 -0
  251. data/resources/configuration/cisco-n9k-vpc.erb +62 -0
  252. data/resources/configuration/cisco-server-port.erb +43 -0
  253. data/resources/configuration/h3c-bootstrap.erb +108 -0
  254. data/resources/configuration/h3c-irf.erb +57 -0
  255. data/resources/configuration/h3c-server-port.erb +36 -0
  256. data/resources/configuration/hillstone-ipsec-vpn.erb +68 -0
  257. data/resources/configuration/hillstone-policy.erb +31 -0
  258. data/resources/configuration/paloalto-ipsec-vpn.erb +39 -0
  259. data/resources/configuration/radware-load-balancer.erb +26 -0
  260. data/resources/textfsm/cisco_cdp_neighbors_detail.textfsm +7 -0
  261. data/resources/textfsm/cisco_ios_running_config_interfaces.textfsm +12 -0
  262. data/resources/textfsm/cisco_ios_show_ip_interface_brief.textfsm +7 -0
  263. data/resources/textfsm/h3c_interface_descriptions.textfsm +11 -0
  264. data/resources/textfsm/h3c_lldp_local_first.textfsm +8 -0
  265. data/resources/textfsm/h3c_lldp_name_first.textfsm +8 -0
  266. data/resources/textfsm/hillstone_interface_descriptions.textfsm +11 -0
  267. data/resources/textfsm/hillstone_lldp_neighbors.textfsm +7 -0
  268. data/resources/textfsm/index +8 -0
  269. data/resources/textfsm/palo_alto_interface_descriptions.textfsm +6 -0
  270. data/resources/textfsm/palo_alto_lldp_neighbors.textfsm +11 -0
  271. data/resources/textfsm/radware_port_names.textfsm +13 -0
  272. metadata +588 -27
  273. data/.rubocop.yml +0 -13
  274. data/CODE_OF_CONDUCT.md +0 -84
  275. data/Gemfile +0 -10
  276. data/Gemfile.lock +0 -40
  277. data/Rakefile +0 -8
  278. data/bin/console +0 -15
  279. data/bin/setup +0 -8
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Netstack
4
+ module Wireless
5
+ class SecurityPolicy < Data.define(:key, :global_settings, :blacklist, :whitelist, :blocked_clients)
6
+ def initialize(key:, global_settings: {}, blacklist: [], whitelist: [], blocked_clients: [])
7
+ key = Values.text(key, name: "wireless record key")
8
+ super(**Values.copy(key: key, global_settings: global_settings, blacklist: blacklist, whitelist: whitelist, blocked_clients: blocked_clients))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Netstack
4
+ module Wireless
5
+ class ServiceTemplate < Data.define(:key, :name, :ssid, :vlan_id, :enabled, :authentication_type, :configuration)
6
+ def initialize(key:, name:, ssid: nil, vlan_id: nil, enabled: nil, authentication_type: nil, configuration: {})
7
+ key = Values.text(key, name: "wireless record key")
8
+ super(**Values.copy(key: key, name: name, ssid: ssid, vlan_id: vlan_id, enabled: enabled, authentication_type: authentication_type, configuration: configuration))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Netstack
4
+ module Wireless
5
+ # One service template binding per radio; VLAN is a property of that identity.
6
+ module ServiceTemplateBindings
7
+ module_function
8
+
9
+ def identity(binding)
10
+ unless binding.is_a?(Hash) && binding[:name].is_a?(String) && !binding[:name].strip.empty?
11
+ raise InvalidSnapshot, "invalid service template binding"
12
+ end
13
+ binding[:name].strip.downcase
14
+ end
15
+
16
+ def validate!(bindings)
17
+ raise InvalidSnapshot, "service template bindings must be an Array" unless bindings.is_a?(Array)
18
+ keys = bindings.map { |binding| identity(binding) }
19
+ raise InvalidSnapshot, "duplicate service template binding" unless keys.uniq.size == keys.size
20
+ end
21
+
22
+ def merge(previous, incoming)
23
+ validate!(previous)
24
+ validate!(incoming)
25
+ values = previous.to_h { |binding| [identity(binding), binding] }
26
+ incoming.each do |binding|
27
+ key = identity(binding)
28
+ values[key] = values.key?(key) ? values[key].merge(binding.reject { |_, value| value.nil? }) : binding
29
+ end
30
+ values.values
31
+ end
32
+
33
+ def overlay(explicit, inherited)
34
+ validate!(explicit)
35
+ validate!(inherited)
36
+ (explicit + inherited).uniq { |binding| identity(binding) }
37
+ end
38
+
39
+ def validate_radios!(radios)
40
+ raise InvalidSnapshot, "configured radios must be a Hash" unless radios.is_a?(Hash)
41
+ radios.each_value { |settings| validate_settings!(settings) }
42
+ end
43
+
44
+ def validate_settings!(settings)
45
+ raise InvalidSnapshot, "radio configuration must be a Hash" unless settings.is_a?(Hash)
46
+ validate!(settings[:service_templates]) if settings.key?(:service_templates)
47
+ end
48
+
49
+ def validate_groups!(groups)
50
+ groups.each do |group|
51
+ group.model_profiles.each_value { |profile| validate_radios!(profile.fetch(:radios, {})) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Netstack
4
+ module Wireless
5
+ # 单次观察的完整性与身份边界;未传入的维度明确标记为未请求。
6
+ # 只有相关维度完整时,才能用缺失关系判定对象不存在。
7
+ class Snapshot < Data.define(:target, :dimensions, :observed_at)
8
+ RECORD_TYPES = {
9
+ access_points: AccessPoint, radios: Radio, clients: Client, bsses: Bss, configuration: Configuration,
10
+ ap_groups: ApGroup, service_templates: ServiceTemplate, dot1x_connections: AuthenticationConnection,
11
+ mab_connections: AuthenticationConnection, blocked_clients: Hash, lldp: Neighbor
12
+ }.freeze
13
+ DIMENSIONS = RECORD_TYPES.keys.freeze
14
+
15
+ def initialize(target:, dimensions:, observed_at: Time.now.utc)
16
+ raise InvalidSnapshot, "wireless target must be a Target" unless target.is_a?(Target)
17
+ raise InvalidSnapshot, "wireless dimensions must be an Array" unless dimensions.is_a?(Array)
18
+ raise InvalidSnapshot, "wireless dimensions must be Dimension values" unless dimensions.all?(Dimension)
19
+ raise InvalidSnapshot, "duplicate wireless dimension" unless dimensions.map(&:name).uniq.size == dimensions.size
20
+ raise InvalidSnapshot, "unknown wireless dimension" unless (dimensions.map(&:name) - DIMENSIONS).empty?
21
+
22
+ dimensions = DIMENSIONS.map { |name| dimensions.find { |dimension| dimension.name == name } || Dimension.new(name: name, status: :not_requested) }
23
+ dimensions.each { |dimension| validate_dimension!(dimension) }
24
+ validate_relationships!(dimensions)
25
+ validate_radio_and_bss_relationships!(dimensions)
26
+ observed_at = Values.time(observed_at, name: "wireless snapshot observation")
27
+ if dimensions.any? { |dimension| dimension.completed_at && dimension.completed_at > observed_at }
28
+ raise InvalidSnapshot, "wireless observation precedes collection completion"
29
+ end
30
+ super(target: Values.copy(target), dimensions: Values.copy(dimensions), observed_at: observed_at)
31
+ end
32
+
33
+ def dimension(name) = dimensions.find { |dimension| dimension.name == name.to_sym } || raise(InputError, "unknown wireless dimension")
34
+ def records(name) = dimension(name).records
35
+ def complete? = dimensions.reject { |dimension| dimension.status == :not_requested }.all?(&:complete?) && dimensions.any?(&:complete?)
36
+ def view = View.new(self)
37
+
38
+ private
39
+
40
+ def validate_dimension!(dimension)
41
+ type = RECORD_TYPES.fetch(dimension.name)
42
+ raise InvalidSnapshot, "invalid wireless record type" unless dimension.records.all?(type)
43
+ keys = dimension.records.map { |record| record.is_a?(Hash) ? record[:key] : record.key }
44
+ raise InvalidSnapshot, "missing or duplicate wireless identity" if keys.any? { |key| !key.is_a?(String) || key.empty? } || keys.uniq.size != keys.size
45
+ dimension.records.each { |record| validate_identity!(record) }
46
+ unique!(dimension.records, :ap_id, :mac_address, :serial_id) if dimension.name == :access_points
47
+ unique!(dimension.records, :mac_address) if dimension.name == :clients
48
+ unique!(dimension.records, :bssid) if dimension.name == :bsses
49
+ dimension.records.each { |record| ServiceTemplateBindings.validate_settings!(record.configuration) } if dimension.name == :radios
50
+ ServiceTemplateBindings.validate_groups!(dimension.records) if dimension.name == :ap_groups
51
+ return unless dimension.name == :configuration
52
+ raise InvalidSnapshot, "multiple wireless configurations" if dimension.records.size > 1
53
+ config = dimension.records.first
54
+ return unless config
55
+ unique!(config.ap_groups, :name)
56
+ unique!(config.service_templates, :name)
57
+ config.access_points.each { |name, ap| raise InvalidSnapshot, "configured AP identity mismatch" unless ap[:name] == name }
58
+ config.access_points.each_value { |ap| ServiceTemplateBindings.validate_radios!(ap.fetch(:radios, {})) }
59
+ ServiceTemplateBindings.validate_groups!(config.ap_groups)
60
+ serials = config.access_points.values.filter_map { |ap| ap[:serial_id] }
61
+ raise InvalidSnapshot, "duplicate configured AP serial" unless serials.uniq.size == serials.size
62
+
63
+
64
+ end
65
+
66
+ def validate_identity!(record)
67
+ if record.is_a?(Data)
68
+ record.to_h.each do |field, value|
69
+ next if value.nil?
70
+ if %i[ap_id radio_id client_count online_seconds vlan_id initial_vlan_id authorized_vlan_id].include?(field)
71
+ raise InvalidSnapshot, "invalid wireless numeric field" unless value.is_a?(Integer) && value >= 0 && (!field.to_s.include?("vlan") || value <= 4094)
72
+ elsif %i[mac_address bssid].include?(field)
73
+ raise InvalidSnapshot, "noncanonical wireless MAC identity" unless Normalizer.mac(value) == value
74
+ end
75
+ end
76
+ end
77
+ expected = case record
78
+ when AccessPoint then Normalizer.key(Values.text(record.name, name: "AP name"))
79
+ when Radio then Normalizer.key(Values.text(record.ap_name, name: "radio AP name"), Values.integer(record.radio_id, name: "radio ID", min: 0))
80
+ when Client then Normalizer.key(Normalizer.mac(record.mac_address))
81
+ when Bss then Normalizer.key(Normalizer.mac(record.bssid))
82
+ when ApGroup, ServiceTemplate then Normalizer.key(Values.text(record.name, name: "wireless name"))
83
+ when AuthenticationConnection
84
+ raise InvalidSnapshot, "unknown authentication kind" unless %i[dot1x mab].include?(record.kind)
85
+ Normalizer.key(record.kind, Normalizer.mac(record.mac_address), record.interface, record.bssid)
86
+ when Neighbor then Normalizer.key(record.ap_name, record.local_interface, record.remote_chassis_id, record.remote_interface)
87
+ when Hash then Normalizer.key(Normalizer.mac(record.fetch(:mac_address)))
88
+ when Configuration then "controller"
89
+ end
90
+ raise InvalidSnapshot, "wireless record key does not match its identity" unless expected == (record.is_a?(Hash) ? record[:key] : record.key)
91
+ rescue InputError, KeyError
92
+ raise InvalidSnapshot, "invalid wireless identity"
93
+ end
94
+
95
+ def unique!(records, *fields)
96
+ fields.each do |field|
97
+ values = records.filter_map { |record| record.public_send(field) }
98
+ raise InvalidSnapshot, "duplicate wireless #{field}" unless values.uniq.size == values.size
99
+ end
100
+ end
101
+
102
+ def validate_radio_and_bss_relationships!(dimensions)
103
+ by_name = dimensions.to_h { |dimension| [dimension.name, dimension] }
104
+ radios = by_name.fetch(:radios)
105
+ radio_keys = radios.records.map { |radio| [radio.ap_name, radio.radio_id] }
106
+ bsses = by_name.fetch(:bsses)
107
+ by_bssid = bsses.records.to_h { |bss| [bss.bssid, bss] }
108
+ %i[clients bsses dot1x_connections mab_connections].each do |name|
109
+ by_name.fetch(name).records.each do |record|
110
+ if radios.complete? && record.ap_name && record.radio_id && !radio_keys.include?([record.ap_name, record.radio_id])
111
+ raise InvalidSnapshot, "wireless record refers to an absent radio"
112
+ end
113
+ next if record.is_a?(Bss) || record.bssid.nil?
114
+ bss = by_bssid[record.bssid]
115
+ if bss
116
+ %i[ap_name ap_id radio_id ssid].each do |field|
117
+ if record.public_send(field) && bss.public_send(field) && record.public_send(field) != bss.public_send(field)
118
+ raise InvalidSnapshot, "wireless record and BSS identity disagree"
119
+ end
120
+ end
121
+ elsif bsses.complete? && record.is_a?(Client)
122
+ raise InvalidSnapshot, "wireless client refers to an absent BSS"
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def validate_relationships!(dimensions)
129
+ aps = dimensions.find { |dimension| dimension.name == :access_points }
130
+ names = aps.records.to_h { |ap| [ap.name, ap] }
131
+ ids = aps.records.filter_map { |ap| [ap.ap_id, ap] if ap.ap_id }.to_h
132
+ dimensions.reject { |dimension| dimension.name == :access_points }.each do |dimension|
133
+ dimension.records.each do |record|
134
+ next unless record.respond_to?(:ap_name) || record.respond_to?(:ap_id)
135
+ name = record.respond_to?(:ap_name) ? record.ap_name : nil
136
+ id = record.respond_to?(:ap_id) ? record.ap_id : nil
137
+ by_name = names[name]
138
+ by_id = ids[id]
139
+ if (by_name && by_id && by_name != by_id) || (by_name && id && by_name.ap_id && by_name.ap_id != id) || (by_id && name && by_id.name != name)
140
+ raise InvalidSnapshot, "AP name and ID refer to different access points"
141
+ end
142
+ if aps.complete? && (name || id) && !by_name && !by_id
143
+ raise InvalidSnapshot, "wireless record refers to an absent access point"
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Netstack
4
+ module Wireless
5
+ # 保留记录与单次观察各自独立;失败观察没有记录,但不能清空已有状态。
6
+ class State < Data.define(:target, :records, :watermarks)
7
+ def initialize(target:, records: {}, watermarks: {})
8
+ raise InvalidSnapshot, "wireless state target must be a Target" unless target.is_a?(Target)
9
+ raise InvalidSnapshot, "invalid wireless state dimensions" unless (records.keys - Snapshot::DIMENSIONS).empty? && (watermarks.keys - Snapshot::DIMENSIONS).empty?
10
+
11
+ dimensions = records.map { |name, values| Dimension.new(name: name, status: :partial, records: values) }
12
+ Snapshot.new(target: target, dimensions: dimensions)
13
+ super(target: Values.copy(target), records: Values.copy(records),
14
+ watermarks: Values.copy(watermarks.transform_values { |time| Values.time(time, name: "wireless watermark") }))
15
+ end
16
+
17
+ def self.from_snapshot(snapshot)
18
+ new(target: snapshot.target, records: snapshot.dimensions.to_h { |dimension| [dimension.name, dimension.records] },
19
+ watermarks: snapshot.dimensions.filter_map { |dimension| [dimension.name, dimension.completed_at] if dimension.completed_at }.to_h)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/deep_merge"
4
+
5
+ module Netstack
6
+ module Wireless
7
+ # A derived controller view. Original runtime/configuration dimensions retain their own completeness.
8
+ class View
9
+ attr_reader :access_points, :radios, :clients, :bsses, :ap_groups, :service_templates, :security_policy, :diagnostics
10
+
11
+ def initialize(snapshot)
12
+ @snapshot = snapshot
13
+ @diagnostics = []
14
+ configuration = snapshot.records(:configuration).first || Configuration.new
15
+ @ap_groups = (snapshot.records(:ap_groups) + configuration.ap_groups).uniq(&:name)
16
+ @service_templates = (snapshot.records(:service_templates) + configuration.service_templates).uniq(&:name)
17
+ runtime = snapshot.records(:access_points).to_h { |ap| [ap.name, ap] }
18
+ @access_points = (runtime.keys | configuration.access_points.keys).sort.map do |name|
19
+ configured = configuration.access_points.fetch(name, {})
20
+ ap = runtime[name] || AccessPoint.new(key: Normalizer.key(name), name: name)
21
+ group = group_for(ap, configured)
22
+ settings = (group&.configuration || {}).deep_merge(configured.reject { |key, _| %i[name model serial_id radios].include?(key) })
23
+ ap.with(model: configured[:model] || ap.model, serial_id: configured[:serial_id] || ap.serial_id,
24
+ group_name: ap.group_name || group&.name, configuration: ap.configuration.deep_merge(settings))
25
+ end
26
+ @radios = build_radios(configuration)
27
+ @bsses = snapshot.records(:bsses).map do |bss|
28
+ template = service_template_for(bss)
29
+ matched_ap = access_points.find { |candidate| candidate.ap_id && candidate.ap_id == bss.ap_id }
30
+ bss.with(service_template: bss.service_template || template&.name, ap_name: bss.ap_name || matched_ap&.name)
31
+ end
32
+ @security_policy = SecurityPolicy.new(key: "controller", global_settings: configuration.global_settings,
33
+ blacklist: configuration.blacklist, whitelist: configuration.whitelist, blocked_clients: snapshot.records(:blocked_clients))
34
+ @clients = Values.copy(snapshot.records(:clients).map { |client| enrich_client(client) })
35
+ @access_points = Values.copy(@access_points)
36
+ @radios = Values.copy(@radios)
37
+ @bsses = Values.copy(@bsses)
38
+ @ap_groups = Values.copy(@ap_groups)
39
+ @service_templates = Values.copy(@service_templates)
40
+ @diagnostics = Values.copy(@diagnostics)
41
+ freeze
42
+ end
43
+
44
+ def clients_for(ap_name:, radio_id: nil)
45
+ clients.select { |client| client.ap_name == ap_name && (radio_id.nil? || client.radio_id == radio_id) }.freeze
46
+ end
47
+
48
+ def authentications_for(client)
49
+ %i[dot1x_connections mab_connections].flat_map { |name| @snapshot.records(name) }.select do |connection|
50
+ connection.mac_address == client.mac_address && (!connection.ap_name || !client.ap_name || connection.ap_name == client.ap_name) &&
51
+ (!connection.bssid || !client.bssid || connection.bssid == client.bssid) &&
52
+ (!connection.ap_id || !client.ap_id || connection.ap_id == client.ap_id) &&
53
+ (!connection.radio_id || !client.radio_id || connection.radio_id == client.radio_id)
54
+ end.freeze
55
+ end
56
+
57
+ def neighbors_for(ap_name:) = @snapshot.records(:lldp).select { |neighbor| neighbor.ap_name == ap_name }.freeze
58
+ def radios_for(ap_name:) = radios.select { |radio| radio.ap_name == ap_name }.freeze
59
+ def bsses_for(ap_name:) = bsses.select { |bss| bss.ap_name == ap_name }.freeze
60
+
61
+ def blocked?(mac)
62
+ mac = Normalizer.mac(mac)
63
+ (security_policy.blacklist + security_policy.blocked_clients).any? { |record| record[:mac_address] == mac }
64
+ end
65
+
66
+ def whitelisted?(mac)
67
+ mac = Normalizer.mac(mac)
68
+ security_policy.whitelist.any? { |record| record[:mac_address] == mac }
69
+ end
70
+
71
+ private
72
+
73
+ def group_for(access_point, configured)
74
+ return ap_groups.find { |group| group.name == access_point.group_name } if access_point.group_name
75
+
76
+ groups = ap_groups.select { |group| group.ap_names.include?(access_point.name) || (configured[:serial_id] && group.serial_ids.include?(configured[:serial_id])) }
77
+ if groups.size > 1
78
+ @diagnostics << { code: :ambiguous_ap_group, record_key: access_point.key }
79
+ return nil
80
+ end
81
+ groups.first
82
+ end
83
+
84
+ def build_radios(configuration)
85
+ runtime = @snapshot.records(:radios).group_by(&:ap_name)
86
+ access_points.flat_map do |ap|
87
+ configured = configuration.access_points.fetch(ap.name, {})
88
+ group = ap_groups.find { |candidate| candidate.name == ap.group_name }
89
+ inherited = group&.model_profiles&.dig(ap.model, :radios) || {}
90
+ explicit = configured.fetch(:radios, {})
91
+ observed = (runtime[ap.name] || []).to_h { |radio| [radio.radio_id, radio] }
92
+ (inherited.keys | explicit.keys | observed.keys).sort.map do |id|
93
+ settings = inherited.fetch(id, {}).deep_merge(explicit.fetch(id, {}))
94
+ bindings = ServiceTemplateBindings.overlay(explicit.dig(id, :service_templates) || [], inherited.dig(id, :service_templates) || [])
95
+ settings[:service_templates] = bindings if !bindings.empty?
96
+ radio = observed[id] || Radio.new(key: Normalizer.key(ap.name, id), ap_name: ap.name, radio_id: id)
97
+ radio.with(configuration: settings.deep_merge(radio.configuration))
98
+ end
99
+ end
100
+ end
101
+
102
+ def enrich_client(client)
103
+ bss = bsses.find { |candidate| candidate.bssid == client.bssid } if client.bssid
104
+ candidates = if bss&.service_template
105
+ service_templates.select { |template| template.name == bss.service_template }
106
+ else
107
+ service_templates.select { |template| client.ssid && template.ssid == client.ssid }
108
+ end
109
+ template = candidates.size == 1 ? candidates.first : nil
110
+ @diagnostics << { code: :ambiguous_service_template, record_key: client.key } if candidates.size > 1
111
+ connections = authentications_for(client)
112
+ auth_type = if connections.any? { |connection| connection.kind == :dot1x }
113
+ :dot1x
114
+ elsif connections.any? { |connection| connection.kind == :mab }
115
+ :mab
116
+ elsif template
117
+ template.authentication_type
118
+ else
119
+ runtime_authentication_type(client)
120
+ end
121
+ matched_ap = access_points.find { |candidate| candidate.ap_id && candidate.ap_id == client.ap_id }
122
+ client.with(ap_name: client.ap_name || matched_ap&.name, authentication: client.authentication.merge(
123
+ authentication_type: auth_type, service_template: template&.name,
124
+ configured_security: template&.configuration, connection_keys: connections.map(&:key)),
125
+ attributes: client.attributes.merge(blocked: blocked?(client.mac_address), whitelisted: whitelisted?(client.mac_address)))
126
+ end
127
+
128
+ def runtime_authentication_type(client)
129
+ mode = client.authentication[:akm_mode].to_s
130
+ return :dot1x if /(?:802\.1x|dot1x)/i.match?(mode)
131
+ return :psk if /psk/i.match?(mode)
132
+
133
+ nil
134
+ end
135
+
136
+ def service_template_for(bss)
137
+ candidates = if bss.service_template
138
+ service_templates.select { |template| template.name == bss.service_template }
139
+ elsif bss.ssid
140
+ service_templates.select { |template| template.ssid == bss.ssid }
141
+ else
142
+ []
143
+ end
144
+ if candidates.size > 1
145
+ @diagnostics << { code: :ambiguous_service_template, record_key: bss.key }
146
+ nil
147
+ else
148
+ @diagnostics << { code: :unknown_service_template, record_key: bss.key } if candidates.empty? && bss.service_template
149
+ candidates.first
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
data/lib/netstack.rb CHANGED
@@ -1,8 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "netstack/version"
3
+ require "zeitwerk"
4
4
 
5
+ # 网络观察、可审查的意图与显式的单目标执行。
6
+ # 加载常量不会建立会话,也不会启动由宿主管理的后台任务。
5
7
  module Netstack
6
- class Error < StandardError; end
7
- # Your code goes here...
8
+ LOADER = Zeitwerk::Loader.for_gem
9
+ LOADER.inflector.inflect("textfsm" => "TextFSM", "ipv4" => "IPv4", "ipv6" => "IPv6")
10
+ LOADER.setup
11
+
12
+ def self.eager_load!
13
+ LOADER.eager_load
14
+ end
8
15
  end
@@ -0,0 +1,118 @@
1
+ ! Cisco NX-OS bootstrap: <%= hostname %>
2
+ ! Device role: <%= device_role %>
3
+ configure terminal
4
+ hostname <%= hostname %>
5
+ clock timezone <%= system.timezone %> 8 0
6
+ ip domain-name <%= system.domain_name %>
7
+ feature tacacs+
8
+ feature interface-vlan
9
+ feature lacp
10
+ feature lldp
11
+ feature netconf
12
+ <% if underlay_enabled %>feature ospf
13
+ <% end %><% if overlay_enabled %>feature bgp
14
+ feature nv overlay
15
+ feature vn-segment-vlan-based
16
+ nv overlay evpn
17
+ <% end %><% if vpc_enabled %>feature vpc
18
+ cfs ipv4 distribute
19
+ <% end %>interface mgmt0
20
+ vrf member management
21
+ ip address <%= mgmt %>
22
+ no shutdown
23
+ vrf context management
24
+ ip route 0.0.0.0/0 <%= mgmt_gateway %>
25
+ <% system.dns_servers.each do |server| %>ip name-server <%= server %>
26
+ <% end %><% system.ntp_servers.each do |server| %>ntp server <%= server %> use-vrf management
27
+ <% end %><% system.syslog_servers.each do |server| %>logging server <%= server %> use-vrf management
28
+ <% end %>snmp-server community <%= system.snmp_community %> RO
29
+ snmp-server location <%= snmp_location %>
30
+ <% system.tacacs_entries.each do |server| %>tacacs server <%= server.name %>
31
+ address ipv4 <%= server.address %>
32
+ key <%= system.tacacs_secret %>
33
+ <% end %>aaa group server tacacs+ TACACS
34
+ <% system.tacacs_entries.each do |server| %> server name <%= server.name %>
35
+ <% end %>aaa authentication login default group TACACS local
36
+ aaa authorization commands default group TACACS local
37
+ ip access-list SSH-MANAGEMENT
38
+ <% system.ssh_acl_entries.each do |entry| %> permit ip <%= entry.wildcard %> any
39
+ <% end %>line vty
40
+ session-limit 16
41
+ exec-timeout <%= system.vty_timeout_minutes %>
42
+ transport input ssh
43
+ ip access-class SSH-MANAGEMENT in
44
+ <% system.local_users.each do |user| %>username <%= user.username %> privilege <%= user.cisco_privilege %> secret <%= user.password %>
45
+ <% end %><% if system.base_security_enabled %>password strength-check
46
+ <% else %>no password strength-check
47
+ <% end %>
48
+ <% if system.disable_http %>no feature http-server
49
+ <% end %><% if underlay_enabled %>interface loopback0
50
+ description ROUTER_ID
51
+ ip address <%= loopback %>
52
+ ip router ospf <%= ospf_process_id %> area <%= ospf_area %>
53
+ <% fabric_links.each do |link| %>interface <%= link.interface %>
54
+ description <%= link.description %>
55
+ mtu <%= fabric_mtu %>
56
+ no switchport
57
+ port-type fabric
58
+ ip address <%= link.address %>
59
+ ip ospf network point-to-point
60
+ ip router ospf <%= ospf_process_id %> area <%= ospf_area %>
61
+ no shutdown
62
+ <% end %>router ospf <%= ospf_process_id %>
63
+ router-id <%= loopback_ip %>
64
+ log-adjacency-changes
65
+ <% end %><% if nve_enabled %>interface loopback1
66
+ description NVE_SOURCE
67
+ ip address <%= vtep_loopback %>
68
+ <% if vtep_secondary_enabled %> ip address <%= vtep_secondary %> secondary
69
+ <% end %> ip ospf network point-to-point
70
+ ip router ospf <%= ospf_process_id %> area <%= ospf_area %>
71
+ interface nve1
72
+ no shutdown
73
+ host-reachability protocol bgp
74
+ advertise virtual-rmac
75
+ source-interface loopback1
76
+ <% end %><% if overlay_enabled %>router bgp <%= local_as %>
77
+ router-id <%= loopback_ip %>
78
+ log-neighbor-changes
79
+ address-family ipv4 unicast
80
+ address-family l2vpn evpn
81
+ retain route-target all
82
+ <% if advertise_pip %> advertise-pip
83
+ <% end %> template peer VTEP_TEMPLATE
84
+ remote-as <%= remote_as %>
85
+ update-source loopback0
86
+ address-family ipv4 unicast
87
+ send-community
88
+ send-community extended
89
+ address-family l2vpn evpn
90
+ send-community
91
+ send-community extended
92
+ <% bgp_neighbors.each do |neighbor| %> neighbor <%= neighbor.address %>
93
+ inherit peer VTEP_TEMPLATE
94
+ <% if route_reflector %> address-family l2vpn evpn
95
+ route-reflector-client
96
+ <% end %><% end %><% external_bgp_neighbors.each do |neighbor| %> neighbor <%= neighbor.address %>
97
+ remote-as <%= neighbor.remote_as %>
98
+ <% if neighbor.description %> description <%= neighbor.description %>
99
+ <% end %> address-family ipv4 unicast
100
+ <% end %><% end %><% if vpc_enabled %>vpc domain <%= vpc_domain_id %>
101
+ peer-switch
102
+ role priority <%= vpc_role_priority %>
103
+ peer-keepalive destination <%= vpc_peer_keepalive_destination %> source <%= vpc_peer_keepalive_source %>
104
+ <% if vpc_virtual_peer_link_enabled %> virtual peer-link destination <%= vpc_virtual_peer_link_destination %> source <%= vpc_virtual_peer_link_source %> dscp 56
105
+ <% end %> peer-gateway
106
+ layer3 peer-router
107
+ auto-recovery
108
+ fast-convergence
109
+ ip arp synchronize
110
+ interface port-channel<%= vpc_peer_link_port_channel %>
111
+ description VPC_PEER_LINK
112
+ switchport
113
+ switchport mode trunk
114
+ spanning-tree port type network
115
+ vpc peer-link
116
+ no shutdown
117
+ <% end %>end
118
+ copy running-config startup-config
@@ -0,0 +1,40 @@
1
+ configure terminal
2
+ ! Cisco distributed gateways
3
+ ! ============================================================
4
+ ! 1. VLAN -> VNI
5
+ ! ============================================================
6
+ <% gateways.each do |gateway| %>vlan <%= gateway.vlan %>
7
+ <% if gateway.name %> name <%= gateway.name %>
8
+ <% end %> vn-segment <%= gateway.vni %>
9
+ <% end %>
10
+ ! ============================================================
11
+ ! 2. VNI -> EVPN
12
+ ! ============================================================
13
+ evpn
14
+ <% gateways.each do |gateway| %> vni <%= gateway.vni %> l2
15
+ rd auto
16
+ route-target import auto
17
+ route-target export auto
18
+ <% end %>
19
+ ! ============================================================
20
+ ! 3. Gateway prefixes and SVI
21
+ ! ============================================================
22
+ <% gateways.each do |gateway| %>ip prefix-list VXLAN permit <%= gateway.subnet %>
23
+ <% end %><% gateways.each do |gateway| %>interface Vlan<%= gateway.vlan %>
24
+ no shutdown
25
+ mtu <%= gateway.mtu %>
26
+ <% if gateway.acl %> ip access-group <%= gateway.acl %> in
27
+ <% end %> vrf member <%= gateway.vrf %>
28
+ no ip redirects
29
+ ip address <%= gateway.gateway_cidr %>
30
+ fabric forwarding mode anycast-gateway
31
+ <% end %>
32
+ ! ============================================================
33
+ ! 4. VNI -> NVE
34
+ ! ============================================================
35
+ interface nve1
36
+ <% gateways.each do |gateway| %> member vni <%= gateway.vni %>
37
+ suppress-arp
38
+ ingress-replication protocol bgp
39
+ <% end %>end
40
+ copy running-config startup-config
@@ -0,0 +1,62 @@
1
+ ! Cisco N9K vPC pair
2
+ ! Primary switch
3
+ configure terminal
4
+ feature vpc
5
+ hardware access-list tcam region ing-racl 0
6
+ hardware access-list tcam region ing-sup 768
7
+ hardware access-list tcam region ing-flow-redirect 512
8
+ system nve infra-vlans <%= infra_vlan %>
9
+ system nve policy-install-all
10
+ vpc domain <%= vpc_domain_id %>
11
+ peer-switch
12
+ role priority <%= primary_role_priority %>
13
+ peer-keepalive destination <%= secondary_keepalive_ip %> source <%= primary_keepalive_ip %>
14
+ virtual peer-link destination <%= secondary_virtual_peer_link_ip %> source <%= primary_virtual_peer_link_ip %> dscp 56
15
+ delay restore 1
16
+ peer-gateway
17
+ layer3 peer-router
18
+ auto-recovery
19
+ fast-convergence
20
+ ip arp synchronize
21
+ interface port-channel<%= vpc_peer_link_port_channel %>
22
+ description VPC_PEER_LINK
23
+ switchport
24
+ switchport mode trunk
25
+ spanning-tree port type network
26
+ vpc peer-link
27
+ no shutdown
28
+ <% fabric_interfaces.each do |interface| %>interface <%= interface %>
29
+ port-type fabric
30
+ <% end %>end
31
+ copy running-config startup-config
32
+
33
+ ! Secondary switch
34
+ configure terminal
35
+ feature vpc
36
+ hardware access-list tcam region ing-racl 0
37
+ hardware access-list tcam region ing-sup 768
38
+ hardware access-list tcam region ing-flow-redirect 512
39
+ system nve infra-vlans <%= infra_vlan %>
40
+ system nve policy-install-all
41
+ vpc domain <%= vpc_domain_id %>
42
+ peer-switch
43
+ role priority <%= secondary_role_priority %>
44
+ peer-keepalive destination <%= primary_keepalive_ip %> source <%= secondary_keepalive_ip %>
45
+ virtual peer-link destination <%= primary_virtual_peer_link_ip %> source <%= secondary_virtual_peer_link_ip %> dscp 56
46
+ delay restore 1
47
+ peer-gateway
48
+ layer3 peer-router
49
+ auto-recovery
50
+ fast-convergence
51
+ ip arp synchronize
52
+ interface port-channel<%= vpc_peer_link_port_channel %>
53
+ description VPC_PEER_LINK
54
+ switchport
55
+ switchport mode trunk
56
+ spanning-tree port type network
57
+ vpc peer-link
58
+ no shutdown
59
+ <% fabric_interfaces.each do |interface| %>interface <%= interface %>
60
+ port-type fabric
61
+ <% end %>end
62
+ copy running-config startup-config