diff --git a/filter_plugins/netbox.py b/filter_plugins/netbox.py index 02ad61a..0762cc1 100644 --- a/filter_plugins/netbox.py +++ b/filter_plugins/netbox.py @@ -14,36 +14,30 @@ class FilterModule(object): 'iface_vlans': self.iface_vlans } - def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-', max_per_line=None): + def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-', min_join=2): '''Transform [1,2,3,5,7,8,9] into "1-3,5,7-9". - If max_per_line is given, return a list of such strings where each string contains at most max_per_line+1 numbers. - This emulates how VLAN ranges are displayed by FS switches so we can make ansible check mode work correctly. + Do not create a range from fewer than min_join consecutive numbers. ''' if sort: nums = sorted(nums) - i = 0 - lines = [] - line = [] - nums_in_line = 0 - while i < len(nums): - j = i + 1 - while j < len(nums) and nums[j]-nums[i] == j-i: - j += 1 - if j > i+1: - line += [f'{nums[i]}{range_delimiter}{nums[j-1]}'] - nums_in_line += 2 + ranges = [] + r = [] + for num in nums: + if r and num > r[-1]+1: + ranges += [r] + r = [] + r += [num] + if r: + ranges += [r] + + def format_range(r): + if len(r) < min_join: + return delimiter.join(str(n) for n in r) else: - line += [f'{nums[i]}'] - nums_in_line += 1 - if max_per_line and nums_in_line >= max_per_line: - lines += [delimiter.join(line)] - line = [] - nums_in_line = 0 - i = j - if line: - lines += [delimiter.join(line)] - return lines if max_per_line else lines[0] + return f'{r[0]}{range_delimiter}{r[-1]}' + + return delimiter.join(format_range(r) for r in ranges) def device_address(self, device): '''Return loopback IP addresses for an L3 attached device''' diff --git a/roles/access/tasks/d-link.yml b/roles/access/tasks/d-link.yml index 413fe87..b9f525c 100644 --- a/roles/access/tasks/d-link.yml +++ b/roles/access/tasks/d-link.yml @@ -13,10 +13,6 @@ set_fact: snmp_hashes: '{{ (snmp_config.stdout | from_yaml).snmpv3.hashes }}' -- name: Get switch facts - cisco.ios.ios_facts: - gather_subset: config - - name: Get SNMP users set_fact: snmp_current: "{{ ansible_net_config | split('\n') | select('match', '^snmp-server user '+manager.snmp_user+' public v3') }}" diff --git a/roles/access/tasks/fs-s5800-48t4s.yml b/roles/access/tasks/fs-s5800-48t4s.yml new file mode 120000 index 0000000..b136bed --- /dev/null +++ b/roles/access/tasks/fs-s5800-48t4s.yml @@ -0,0 +1 @@ +fs.yml \ No newline at end of file diff --git a/roles/access/tasks/main.yml b/roles/access/tasks/main.yml index 0b759f8..c06a8be 100644 --- a/roles/access/tasks/main.yml +++ b/roles/access/tasks/main.yml @@ -11,6 +11,21 @@ set_fact: snmp_engine_id: '{{ (serial | sha1)[:24] }}' +- name: Get switch facts + cisco.ios.ios_facts: + gather_subset: config + +# Determine VLANs to add and remove from switch. +- set_fact: + actual_vlans: "{{ vlans | map(attribute='vid') }}" + switch_vlans: "{{ ansible_net_config | split('\n') + | select('match', '^ *vlan (range )?[0-9]') | map('regex_search', '[0-9,-]+') | join(',') + | default('0', true) | ansible.netcommon.vlan_expander | reject('eq', 0) }}" # vlan_expander barfs on empty string so add/remove a fake VLAN 0 + +- set_fact: + add_vlans: "{{ actual_vlans | difference(switch_vlans) }}" + del_vlans: "{{ switch_vlans | difference(actual_vlans) }}" + - name: Set configuration ansible.netcommon.cli_config: config: '{{ lookup("template", "config-"~manufacturer~"-"~device_type~".j2") }}' @@ -19,7 +34,7 @@ ansible_terminal_stderr_re: [] # some errors are not actually errors register: result # These lines are not displayed by 'sho ru' and always reported as different, so ignore them. - changed_when: result.commands | reject('match', '^(no shutdown|no switchport access vlan|no switchport trunk native vlan|no voice vlan.*|switchport mode access|switchport mode hybrid|interface .*|no enable service web-server https?|no ip dhcp snooping|no ip dhcp snooping trust|no switchport port-security.*)$') + changed_when: result.commands | reject('match', '^(no shutdown|no switchport access vlan|no switchport trunk native vlan|no voice vlan.*|switchport mode access|switchport mode hybrid|interface .*|service http disable|no enable service web-server https?|no ip dhcp snooping|no ip dhcp snooping trust|no switchport port-security.*)$') notify: write config - name: Run model-specific tasks diff --git a/roles/access/templates/config-d-link.j2 b/roles/access/templates/config-d-link.j2 index 1a4b3b5..78680f0 100644 --- a/roles/access/templates/config-d-link.j2 +++ b/roles/access/templates/config-d-link.j2 @@ -10,7 +10,12 @@ port-channel load-balance src-dst-ip ip ssh server -vlan {{ vlans | map(attribute='vid') | compact_numlist }} +{% for vlan in add_vlans %} +vlan {{ vlan }} +{% endfor %} +{% for vlan in del_vlans %} +no vlan {{ vlan }} +{% endfor %} {# bond members #} {% for iface in interfaces | selectattr('lag') %} diff --git a/roles/access/templates/config-fs.j2 b/roles/access/templates/config-fs-s5800-48t4s.j2 similarity index 54% rename from roles/access/templates/config-fs.j2 rename to roles/access/templates/config-fs-s5800-48t4s.j2 index cc17f14..3d5c7e9 100644 --- a/roles/access/templates/config-fs.j2 +++ b/roles/access/templates/config-fs-s5800-48t4s.j2 @@ -1,40 +1,41 @@ hostname {{ inventory_hostname }} -no netconf enable +service http disable +service telnet disable -no enable service telnet-server -no enable service web-server http -no enable service web-server https - -{% for vlan_range in vlans | map(attribute='vid') | union([1]) | compact_numlist(max_per_line=19) %} -vlan range {{ vlan_range }} +vlan database +{% for vlan in add_vlans %} + vlan {{ vlan }} {% endfor %} +{% for vlan in del_vlans %} + no vlan {{ vlan }} +{% endfor %} +exit -{% for iface in interfaces %} -interface {{ iface.name }} -{% if iface.enabled %} no{% endif %} shutdown -{% if iface.lag %} - port-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active - -{% elif iface.mgmt_only %} +{# sort to ensure LAG interfaces are added last #} +{% for iface in interfaces | sort(attribute="type.value") | sort(attribute="mgmt_only") %} +{% if iface.mgmt_only %} {% for address in iface.ip_addresses %} {% set subnet = address.address | ipaddr('subnet') %} {% set prefix = prefixes | selectattr('prefix', '==', subnet) | first %} {% if address.family.value == 4 %} - ip address {{ address.address | ipaddr('address') }} {{ address.address | ipaddr('netmask') }} +management ip address {{ address.address }} {% if prefix.custom_fields.gateway %} - gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') }} +management route add gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') }} {% endif %} {% else %} - ipv6 address {{ address.address | upper }} -{% if prefix.custom_fields.gateway %} - ipv6 gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') | upper }} -{% endif %} +management ipv6 address {{ address.address }} {% endif %} {% endfor %} {% else %} - mtu {{ iface.mtu | default('9216', true) }} + +interface {{ iface.name }} +{% if iface.enabled %} no{% endif %} shutdown +{% if iface.lag %} + channel-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active + +{% else %} {% if iface.mode and iface.mode.value == 'access' %} switchport mode access {% if iface.untagged_vlan and iface.untagged_vlan.vid != 1 %} @@ -46,9 +47,10 @@ interface {{ iface.name }} switchport mode trunk switchport trunk allowed vlan only {{ (iface.tagged_vlans or vlans) | map(attribute='vid') | compact_numlist }} {%- elif iface.mode and iface.mode.value == 'tagged-all' %} - switchport mode uplink - switchport trunk allowed vlan only 2-4094 + switchport mode trunk + switchport trunk allowed vlan all {% endif %} +{% endif %} {% endif %} {% endfor %} diff --git a/roles/access/templates/config-fs-s5860-48xmg-u.j2 b/roles/access/templates/config-fs-s5860-48xmg-u.j2 deleted file mode 120000 index ffb3016..0000000 --- a/roles/access/templates/config-fs-s5860-48xmg-u.j2 +++ /dev/null @@ -1 +0,0 @@ -config-fs.j2 \ No newline at end of file diff --git a/roles/access/templates/config-fs-s5860-48xmg-u.j2 b/roles/access/templates/config-fs-s5860-48xmg-u.j2 new file mode 100644 index 0000000..036b709 --- /dev/null +++ b/roles/access/templates/config-fs-s5860-48xmg-u.j2 @@ -0,0 +1,57 @@ +hostname {{ inventory_hostname }} + +no netconf enable + +no enable service telnet-server +no enable service web-server http +no enable service web-server https + +{% for vlan in add_vlans %} + vlan {{ vlan }} +{% endfor %} +{% for vlan in del_vlans | difference([1]) %} {# VLAN 1 can not be deleted #} + no vlan {{ vlan }} +{% endfor %} + +{% for iface in interfaces %} +interface {{ iface.name }} +{% if iface.enabled %} no{% endif %} shutdown +{% if iface.lag %} + port-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active + +{% elif iface.mgmt_only %} +{% for address in iface.ip_addresses %} +{% set subnet = address.address | ipaddr('subnet') %} +{% set prefix = prefixes | selectattr('prefix', '==', subnet) | first %} +{% if address.family.value == 4 %} + ip address {{ address.address | ipaddr('address') }} {{ address.address | ipaddr('netmask') }} +{% if prefix.custom_fields.gateway %} + gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') }} +{% endif %} +{% else %} + ipv6 address {{ address.address | upper }} +{% if prefix.custom_fields.gateway %} + ipv6 gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') | upper }} +{% endif %} +{% endif %} +{% endfor %} + +{% else %} + mtu {{ iface.mtu | default('9216', true) }} +{% if iface.mode and iface.mode.value == 'access' %} + switchport mode access +{% if iface.untagged_vlan and iface.untagged_vlan.vid != 1 %} + switchport access vlan {{ iface.untagged_vlan.vid }} +{% else %} + no switchport access vlan +{% endif %} +{%- elif iface.mode and iface.mode.value == 'tagged' %} + switchport mode trunk + switchport trunk allowed vlan only {{ (iface.tagged_vlans or vlans) | map(attribute='vid') | compact_numlist }} +{%- elif iface.mode and iface.mode.value == 'tagged-all' %} + switchport mode uplink + switchport trunk allowed vlan only 2-4094 +{% endif %} + +{% endif %} +{% endfor %} diff --git a/roles/leaf/templates/frr.conf.j2 b/roles/leaf/templates/frr.conf.j2 index a30748b..8349ee5 100644 --- a/roles/leaf/templates/frr.conf.j2 +++ b/roles/leaf/templates/frr.conf.j2 @@ -55,8 +55,8 @@ router bgp {{ asn.asn }} {% endfor %} address-family l2vpn evpn neighbor fabric activate -{% for iface in ifaces_evpn|default([]) %} - neighbor {{ iface }} activate +{% for iface in interfaces | selectattr('enabled') | selectattr('name', 'in', ifaces_evpn|default([])) %} + neighbor {{ iface.name }} activate {% endfor %} {% if peer is defined and interfaces | selectattr('mode') %} advertise-all-vni