diff --git a/filter_plugins/netbox.py b/filter_plugins/netbox.py index 0762cc1..02ad61a 100644 --- a/filter_plugins/netbox.py +++ b/filter_plugins/netbox.py @@ -14,30 +14,36 @@ class FilterModule(object): 'iface_vlans': self.iface_vlans } - def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-', min_join=2): + def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-', max_per_line=None): '''Transform [1,2,3,5,7,8,9] into "1-3,5,7-9". - Do not create a range from fewer than min_join consecutive numbers. + 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. ''' if sort: nums = sorted(nums) - 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) + 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 else: - return f'{r[0]}{range_delimiter}{r[-1]}' - - return delimiter.join(format_range(r) for r in ranges) + 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] 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 b9f525c..413fe87 100644 --- a/roles/access/tasks/d-link.yml +++ b/roles/access/tasks/d-link.yml @@ -13,6 +13,10 @@ 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 deleted file mode 120000 index b136bed..0000000 --- a/roles/access/tasks/fs-s5800-48t4s.yml +++ /dev/null @@ -1 +0,0 @@ -fs.yml \ No newline at end of file diff --git a/roles/access/tasks/main.yml b/roles/access/tasks/main.yml index c06a8be..0b759f8 100644 --- a/roles/access/tasks/main.yml +++ b/roles/access/tasks/main.yml @@ -11,21 +11,6 @@ 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") }}' @@ -34,7 +19,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 .*|service http disable|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 .*|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 78680f0..1a4b3b5 100644 --- a/roles/access/templates/config-d-link.j2 +++ b/roles/access/templates/config-d-link.j2 @@ -10,12 +10,7 @@ port-channel load-balance src-dst-ip ip ssh server -{% for vlan in add_vlans %} -vlan {{ vlan }} -{% endfor %} -{% for vlan in del_vlans %} -no vlan {{ vlan }} -{% endfor %} +vlan {{ vlans | map(attribute='vid') | compact_numlist }} {# bond members #} {% for iface in interfaces | selectattr('lag') %} 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 100644 index 036b709..0000000 --- a/roles/access/templates/config-fs-s5860-48xmg-u.j2 +++ /dev/null @@ -1,57 +0,0 @@ -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/access/templates/config-fs-s5860-48xmg-u.j2 b/roles/access/templates/config-fs-s5860-48xmg-u.j2 new file mode 120000 index 0000000..ffb3016 --- /dev/null +++ b/roles/access/templates/config-fs-s5860-48xmg-u.j2 @@ -0,0 +1 @@ +config-fs.j2 \ No newline at end of file diff --git a/roles/access/templates/config-fs-s5800-48t4s.j2 b/roles/access/templates/config-fs.j2 similarity index 54% rename from roles/access/templates/config-fs-s5800-48t4s.j2 rename to roles/access/templates/config-fs.j2 index 3d5c7e9..cc17f14 100644 --- a/roles/access/templates/config-fs-s5800-48t4s.j2 +++ b/roles/access/templates/config-fs.j2 @@ -1,41 +1,40 @@ hostname {{ inventory_hostname }} -service http disable -service telnet disable +no netconf enable -vlan database -{% for vlan in add_vlans %} - vlan {{ vlan }} -{% endfor %} -{% for vlan in del_vlans %} - no vlan {{ vlan }} -{% endfor %} -exit +no enable service telnet-server +no enable service web-server http +no enable service web-server https -{# 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 vlan_range in vlans | map(attribute='vid') | union([1]) | compact_numlist(max_per_line=19) %} +vlan range {{ vlan_range }} +{% 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 %} -management ip address {{ address.address }} + ip address {{ address.address | ipaddr('address') }} {{ address.address | ipaddr('netmask') }} {% if prefix.custom_fields.gateway %} -management route add gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') }} + gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') }} {% endif %} {% else %} -management ipv6 address {{ address.address }} + ipv6 address {{ address.address | upper }} +{% if prefix.custom_fields.gateway %} + ipv6 gateway {{ prefix.custom_fields.gateway.address | ipaddr('address') | upper }} +{% endif %} {% endif %} {% endfor %} {% else %} - -interface {{ iface.name }} -{% if iface.enabled %} no{% endif %} shutdown -{% if iface.lag %} - channel-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active - -{% 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 %} @@ -47,10 +46,9 @@ 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 trunk - switchport trunk allowed vlan all + switchport mode uplink + switchport trunk allowed vlan only 2-4094 {% endif %} -{% endif %} {% endif %} {% endfor %} diff --git a/roles/leaf/templates/frr.conf.j2 b/roles/leaf/templates/frr.conf.j2 index 8349ee5..a30748b 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 interfaces | selectattr('enabled') | selectattr('name', 'in', ifaces_evpn|default([])) %} - neighbor {{ iface.name }} activate +{% for iface in ifaces_evpn|default([]) %} + neighbor {{ iface }} activate {% endfor %} {% if peer is defined and interfaces | selectattr('mode') %} advertise-all-vni