access: fix VLAN database idempotency

Do not try and match the global VLAN list as printed by the switch.
Instead, only try to realize the truth: there may be some VLANs added
and some removed.

We keep the compact_numlist filter and use it instead of the built-in
vlan_parser when listing VLANs for tagged ports. This is because some
switches compact 1,2,4,5,6 as 1-2,4-6 and others as 1,2,4-6 (see next
commit).

All of this should reduce the number of cases where Ansible reports a
change in configuration where there was in fact no change.
This commit is contained in:
Timotej Lazar 2025-09-18 13:45:16 +02:00
parent bd4299732d
commit 6ade4f2f8a
5 changed files with 43 additions and 31 deletions

View file

@ -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'''