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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -3
- data/{LICENSE.txt → LICENSE} +7 -6
- data/NOTICE +25 -0
- data/README.md +58 -20
- data/docs/acceptance.md +92 -0
- data/docs/backups.md +100 -0
- data/docs/configuration.md +198 -0
- data/docs/connector.md +187 -0
- data/docs/ddi.md +142 -0
- data/docs/deployment.md +47 -0
- data/docs/foundations.md +45 -0
- data/docs/implementation.md +68 -0
- data/docs/inventory.md +42 -0
- data/docs/monitoring.md +227 -0
- data/docs/motor_integration.md +96 -0
- data/docs/performance.md +39 -0
- data/docs/release.md +20 -0
- data/docs/vxlan.md +91 -0
- data/docs/wireless.md +81 -0
- data/lib/netstack/backups/artifact.rb +23 -0
- data/lib/netstack/backups/diff.rb +15 -0
- data/lib/netstack/backups/repository.rb +77 -0
- data/lib/netstack/backups/request.rb +78 -0
- data/lib/netstack/circuits/business_calendar.rb +51 -0
- data/lib/netstack/circuits/capacity_review.rb +191 -0
- data/lib/netstack/circuits/endpoint.rb +25 -0
- data/lib/netstack/circuits/monitoring_plan.rb +212 -0
- data/lib/netstack/configuration/artifact.rb +85 -0
- data/lib/netstack/configuration/artifact_resolver.rb +30 -0
- data/lib/netstack/configuration/bootstrap/payload.rb +67 -0
- data/lib/netstack/configuration/bootstrap/validator.rb +224 -0
- data/lib/netstack/configuration/catalog.rb +60 -0
- data/lib/netstack/configuration/generator.rb +87 -0
- data/lib/netstack/configuration/input_contract.rb +82 -0
- data/lib/netstack/configuration/payload.rb +168 -0
- data/lib/netstack/configuration/reader.rb +124 -0
- data/lib/netstack/configuration/support.rb +30 -0
- data/lib/netstack/configuration/system_configuration.rb +56 -0
- data/lib/netstack/configuration/template_manager.rb +174 -0
- data/lib/netstack/configuration/template_store.rb +40 -0
- data/lib/netstack/configuration/validator.rb +566 -0
- data/lib/netstack/connector/authentication.rb +77 -0
- data/lib/netstack/connector/authentication_error.rb +11 -0
- data/lib/netstack/connector/backup.rb +19 -0
- data/lib/netstack/connector/backup_busy.rb +11 -0
- data/lib/netstack/connector/backup_persistence_error.rb +32 -0
- data/lib/netstack/connector/command.rb +63 -0
- data/lib/netstack/connector/command_result.rb +24 -0
- data/lib/netstack/connector/command_timeout.rb +11 -0
- data/lib/netstack/connector/configuration.rb +138 -0
- data/lib/netstack/connector/connection_closed.rb +11 -0
- data/lib/netstack/connector/connection_error.rb +11 -0
- data/lib/netstack/connector/device.rb +284 -0
- data/lib/netstack/connector/device_error.rb +11 -0
- data/lib/netstack/connector/dialogue.rb +55 -0
- data/lib/netstack/connector/error.rb +44 -0
- data/lib/netstack/connector/error_metadata.rb +58 -0
- data/lib/netstack/connector/event.rb +24 -0
- data/lib/netstack/connector/execution.rb +110 -0
- data/lib/netstack/connector/interaction.rb +55 -0
- data/lib/netstack/connector/internal_error.rb +11 -0
- data/lib/netstack/connector/known_hosts.rb +128 -0
- data/lib/netstack/connector/local_backup.rb +63 -0
- data/lib/netstack/connector/log/event.rb +65 -0
- data/lib/netstack/connector/log/formatter.rb +18 -0
- data/lib/netstack/connector/log/messages.rb +54 -0
- data/lib/netstack/connector/log/redacting_writer.rb +33 -0
- data/lib/netstack/connector/log/transcript.rb +35 -0
- data/lib/netstack/connector/log.rb +262 -0
- data/lib/netstack/connector/log_error.rb +11 -0
- data/lib/netstack/connector/login_timeout.rb +11 -0
- data/lib/netstack/connector/output_limit_exceeded.rb +11 -0
- data/lib/netstack/connector/parsing_error.rb +11 -0
- data/lib/netstack/connector/profile/builder.rb +238 -0
- data/lib/netstack/connector/profile.rb +225 -0
- data/lib/netstack/connector/prompt_error.rb +11 -0
- data/lib/netstack/connector/recovery.rb +59 -0
- data/lib/netstack/connector/redactor.rb +100 -0
- data/lib/netstack/connector/response.rb +25 -0
- data/lib/netstack/connector/response_reader.rb +140 -0
- data/lib/netstack/connector/result.rb +50 -0
- data/lib/netstack/connector/running_config/rendered.rb +16 -0
- data/lib/netstack/connector/running_config/strategy.rb +28 -0
- data/lib/netstack/connector/running_config.rb +113 -0
- data/lib/netstack/connector/save_config.rb +23 -0
- data/lib/netstack/connector/script.rb +59 -0
- data/lib/netstack/connector/script_error.rb +11 -0
- data/lib/netstack/connector/script_output_limit_exceeded.rb +11 -0
- data/lib/netstack/connector/session.rb +414 -0
- data/lib/netstack/connector/session_busy.rb +11 -0
- data/lib/netstack/connector/storage/backup_lock.rb +117 -0
- data/lib/netstack/connector/storage/private_file.rb +111 -0
- data/lib/netstack/connector/storage/safe_file.rb +64 -0
- data/lib/netstack/connector/storage/saved_config.rb +76 -0
- data/lib/netstack/connector/terminal_renderer.rb +186 -0
- data/lib/netstack/connector/terminal_text.rb +32 -0
- data/lib/netstack/connector/textfsm.rb +71 -0
- data/lib/netstack/connector/tftp/file_upload.rb +53 -0
- data/lib/netstack/connector/tftp/strategy.rb +67 -0
- data/lib/netstack/connector/tftp.rb +116 -0
- data/lib/netstack/connector/tftp_completion_error.rb +36 -0
- data/lib/netstack/connector/tftp_receipt.rb +54 -0
- data/lib/netstack/connector/tftp_target.rb +65 -0
- data/lib/netstack/connector/topology/checkpoint_error.rb +16 -0
- data/lib/netstack/connector/topology/deployment_adapter.rb +163 -0
- data/lib/netstack/connector/topology/execution_result.rb +34 -0
- data/lib/netstack/connector/topology/immediate_strategy.rb +39 -0
- data/lib/netstack/connector/topology/interface_description.rb +46 -0
- data/lib/netstack/connector/topology/interface_name.rb +60 -0
- data/lib/netstack/connector/topology/plan.rb +105 -0
- data/lib/netstack/connector/topology/strategy.rb +76 -0
- data/lib/netstack/connector/topology.rb +255 -0
- data/lib/netstack/connector/transport_error.rb +11 -0
- data/lib/netstack/connector/transports.rb +201 -0
- data/lib/netstack/connector/underlying_error.rb +25 -0
- data/lib/netstack/connector/unsupported_operation.rb +11 -0
- data/lib/netstack/connector/vendors/cisco_ios/running_config.rb +22 -0
- data/lib/netstack/connector/vendors/cisco_ios/tftp_backup.rb +41 -0
- data/lib/netstack/connector/vendors/cisco_ios/topology.rb +54 -0
- data/lib/netstack/connector/vendors/cisco_ios.rb +44 -0
- data/lib/netstack/connector/vendors/cisco_nxos/running_config.rb +25 -0
- data/lib/netstack/connector/vendors/cisco_nxos/tftp_backup.rb +43 -0
- data/lib/netstack/connector/vendors/cisco_nxos.rb +51 -0
- data/lib/netstack/connector/vendors/h3c/tftp_backup.rb +34 -0
- data/lib/netstack/connector/vendors/h3c/topology.rb +72 -0
- data/lib/netstack/connector/vendors/h3c.rb +75 -0
- data/lib/netstack/connector/vendors/h3c_wireless.rb +13 -0
- data/lib/netstack/connector/vendors/hillstone/running_config.rb +16 -0
- data/lib/netstack/connector/vendors/hillstone/tftp_backup.rb +55 -0
- data/lib/netstack/connector/vendors/hillstone/topology.rb +64 -0
- data/lib/netstack/connector/vendors/hillstone.rb +39 -0
- data/lib/netstack/connector/vendors/huawei/tftp_backup.rb +26 -0
- data/lib/netstack/connector/vendors/huawei.rb +62 -0
- data/lib/netstack/connector/vendors/palo_alto/running_config.rb +77 -0
- data/lib/netstack/connector/vendors/palo_alto/tftp_backup.rb +49 -0
- data/lib/netstack/connector/vendors/palo_alto/topology.rb +77 -0
- data/lib/netstack/connector/vendors/palo_alto.rb +39 -0
- data/lib/netstack/connector/vendors/radware/running_config.rb +21 -0
- data/lib/netstack/connector/vendors/radware/tftp_backup.rb +49 -0
- data/lib/netstack/connector/vendors/radware/topology.rb +19 -0
- data/lib/netstack/connector/vendors/radware.rb +59 -0
- data/lib/netstack/connector/write_timeout.rb +11 -0
- data/lib/netstack/connector.rb +32 -0
- data/lib/netstack/ddi/applier.rb +107 -0
- data/lib/netstack/ddi/deployment.rb +137 -0
- data/lib/netstack/ddi/inputs.rb +61 -0
- data/lib/netstack/ddi/manager.rb +197 -0
- data/lib/netstack/ddi/plan.rb +99 -0
- data/lib/netstack/ddi/scope.rb +127 -0
- data/lib/netstack/ddi/settings.rb +44 -0
- data/lib/netstack/ddi/state.rb +166 -0
- data/lib/netstack/deployment/checkpoint_error.rb +14 -0
- data/lib/netstack/deployment/cli_adapter.rb +64 -0
- data/lib/netstack/deployment/executor.rb +131 -0
- data/lib/netstack/deployment/plan.rb +82 -0
- data/lib/netstack/deployment/receipt.rb +78 -0
- data/lib/netstack/deployment/recovery.rb +19 -0
- data/lib/netstack/deployment/step.rb +16 -0
- data/lib/netstack/dimension.rb +30 -0
- data/lib/netstack/error.rb +14 -0
- data/lib/netstack/input_error.rb +9 -0
- data/lib/netstack/integrations/bluecat/client.rb +84 -0
- data/lib/netstack/integrations/http/client.rb +146 -0
- data/lib/netstack/integrations/http/error.rb +10 -0
- data/lib/netstack/integrations/http/response.rb +28 -0
- data/lib/netstack/integrations/http_delivery/adapter.rb +51 -0
- data/lib/netstack/integrations/http_delivery/client.rb +127 -0
- data/lib/netstack/integrations/http_delivery/result.rb +27 -0
- data/lib/netstack/integrations/oxidized/client.rb +89 -0
- data/lib/netstack/integrations/zabbix/client.rb +211 -0
- data/lib/netstack/integrations/zabbix/error.rb +10 -0
- data/lib/netstack/inventory/address.rb +30 -0
- data/lib/netstack/inventory/changes.rb +13 -0
- data/lib/netstack/inventory/cli_parser.rb +75 -0
- data/lib/netstack/inventory/collector.rb +99 -0
- data/lib/netstack/inventory/device.rb +14 -0
- data/lib/netstack/inventory/interface.rb +25 -0
- data/lib/netstack/inventory/neighbor.rb +15 -0
- data/lib/netstack/inventory/reconciler.rb +74 -0
- data/lib/netstack/inventory/snapshot.rb +69 -0
- data/lib/netstack/inventory/transceiver.rb +23 -0
- data/lib/netstack/ipv4.rb +204 -0
- data/lib/netstack/ipv6.rb +88 -0
- data/lib/netstack/monitoring/applier.rb +179 -0
- data/lib/netstack/monitoring/device_plan.rb +23 -0
- data/lib/netstack/monitoring/host_plan.rb +152 -0
- data/lib/netstack/monitoring/inputs.rb +73 -0
- data/lib/netstack/monitoring/measurements.rb +89 -0
- data/lib/netstack/monitoring/mutation.rb +37 -0
- data/lib/netstack/monitoring/plan.rb +69 -0
- data/lib/netstack/monitoring/probe_plan.rb +19 -0
- data/lib/netstack/monitoring/state_comparison.rb +37 -0
- data/lib/netstack/range_set.rb +181 -0
- data/lib/netstack/routing/lookup.rb +46 -0
- data/lib/netstack/routing/lookup_result.rb +14 -0
- data/lib/netstack/routing/route.rb +32 -0
- data/lib/netstack/routing/snapshot.rb +23 -0
- data/lib/netstack/target.rb +15 -0
- data/lib/netstack/unsupported_capability.rb +9 -0
- data/lib/netstack/values.rb +70 -0
- data/lib/netstack/version.rb +1 -1
- data/lib/netstack/vxlan/auditor.rb +187 -0
- data/lib/netstack/vxlan/cisco_nxos/config_parser.rb +327 -0
- data/lib/netstack/vxlan/cisco_nxos/deployment_adapter.rb +63 -0
- data/lib/netstack/vxlan/cisco_nxos/runtime_parser.rb +182 -0
- data/lib/netstack/vxlan/cisco_nxos/segment_renderer.rb +42 -0
- data/lib/netstack/vxlan/collector.rb +77 -0
- data/lib/netstack/vxlan/fabric.rb +29 -0
- data/lib/netstack/vxlan/finding.rb +20 -0
- data/lib/netstack/vxlan/node.rb +51 -0
- data/lib/netstack/vxlan/route_policy.rb +57 -0
- data/lib/netstack/vxlan/runtime_result.rb +24 -0
- data/lib/netstack/vxlan/segment.rb +35 -0
- data/lib/netstack/vxlan/segment_plan.rb +25 -0
- data/lib/netstack/vxlan/segment_planner.rb +156 -0
- data/lib/netstack/vxlan/snapshot.rb +34 -0
- data/lib/netstack/vxlan/validation.rb +58 -0
- data/lib/netstack/vxlan/verifier.rb +52 -0
- data/lib/netstack/vxlan/vpc_domain.rb +20 -0
- data/lib/netstack/wireless/access_point.rb +12 -0
- data/lib/netstack/wireless/ap_group.rb +12 -0
- data/lib/netstack/wireless/assessment.rb +13 -0
- data/lib/netstack/wireless/assessor.rb +83 -0
- data/lib/netstack/wireless/authentication_connection.rb +12 -0
- data/lib/netstack/wireless/bss.rb +12 -0
- data/lib/netstack/wireless/client.rb +12 -0
- data/lib/netstack/wireless/collector.rb +191 -0
- data/lib/netstack/wireless/configuration.rb +13 -0
- data/lib/netstack/wireless/finding.rb +12 -0
- data/lib/netstack/wireless/h3c/config_parser.rb +229 -0
- data/lib/netstack/wireless/h3c/lldp_parser.rb +69 -0
- data/lib/netstack/wireless/h3c/runtime_parser.rb +240 -0
- data/lib/netstack/wireless/huawei/runtime_parser.rb +83 -0
- data/lib/netstack/wireless/invalid_snapshot.rb +11 -0
- data/lib/netstack/wireless/neighbor.rb +12 -0
- data/lib/netstack/wireless/normalizer.rb +90 -0
- data/lib/netstack/wireless/parse_result.rb +13 -0
- data/lib/netstack/wireless/radio.rb +12 -0
- data/lib/netstack/wireless/reconciler.rb +115 -0
- data/lib/netstack/wireless/reconciliation.rb +11 -0
- data/lib/netstack/wireless/security_policy.rb +12 -0
- data/lib/netstack/wireless/service_template.rb +12 -0
- data/lib/netstack/wireless/service_template_bindings.rb +56 -0
- data/lib/netstack/wireless/snapshot.rb +150 -0
- data/lib/netstack/wireless/state.rb +23 -0
- data/lib/netstack/wireless/view.rb +154 -0
- data/lib/netstack.rb +10 -3
- data/resources/configuration/cisco-bootstrap.erb +118 -0
- data/resources/configuration/cisco-distributed-gateway.erb +40 -0
- data/resources/configuration/cisco-n9k-vpc.erb +62 -0
- data/resources/configuration/cisco-server-port.erb +43 -0
- data/resources/configuration/h3c-bootstrap.erb +108 -0
- data/resources/configuration/h3c-irf.erb +57 -0
- data/resources/configuration/h3c-server-port.erb +36 -0
- data/resources/configuration/hillstone-ipsec-vpn.erb +68 -0
- data/resources/configuration/hillstone-policy.erb +31 -0
- data/resources/configuration/paloalto-ipsec-vpn.erb +39 -0
- data/resources/configuration/radware-load-balancer.erb +26 -0
- data/resources/textfsm/cisco_cdp_neighbors_detail.textfsm +7 -0
- data/resources/textfsm/cisco_ios_running_config_interfaces.textfsm +12 -0
- data/resources/textfsm/cisco_ios_show_ip_interface_brief.textfsm +7 -0
- data/resources/textfsm/h3c_interface_descriptions.textfsm +11 -0
- data/resources/textfsm/h3c_lldp_local_first.textfsm +8 -0
- data/resources/textfsm/h3c_lldp_name_first.textfsm +8 -0
- data/resources/textfsm/hillstone_interface_descriptions.textfsm +11 -0
- data/resources/textfsm/hillstone_lldp_neighbors.textfsm +7 -0
- data/resources/textfsm/index +8 -0
- data/resources/textfsm/palo_alto_interface_descriptions.textfsm +6 -0
- data/resources/textfsm/palo_alto_lldp_neighbors.textfsm +11 -0
- data/resources/textfsm/radware_port_names.textfsm +13 -0
- metadata +588 -27
- data/.rubocop.yml +0 -13
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -40
- data/Rakefile +0 -8
- data/bin/console +0 -15
- 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
|
-
|
|
3
|
+
require "zeitwerk"
|
|
4
4
|
|
|
5
|
+
# 网络观察、可审查的意图与显式的单目标执行。
|
|
6
|
+
# 加载常量不会建立会话,也不会启动由宿主管理的后台任务。
|
|
5
7
|
module Netstack
|
|
6
|
-
|
|
7
|
-
|
|
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
|