facts: Allow multiple entries per iface (#6)

* facts: Allow multiple entries per iface

There can be multiple addresses per interface, as well as multiple
neighbors.

This changes the format of "ansible_net_neighbors" to list instead of
dict, because the old format could not store multiple neighbors per
interface.

Also, this fixes a crash when the ipv6 module is not loaded, because the
error "bad command name" was being parsed with interface=None

* facts: Fix tests

* facts: Add changelog fragment
This commit is contained in:
Jan-Philipp Litza 2020-05-18 17:42:17 +02:00 committed by Felix Fontein
parent 3634150468
commit d26dfa16c7
2 changed files with 27 additions and 59 deletions

View file

@ -248,7 +248,7 @@ class Interfaces(FactsBase):
self.facts['interfaces'] = dict()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
self.facts['neighbors'] = dict()
self.facts['neighbors'] = list()
data = self.responses[0]
if data:
@ -257,41 +257,31 @@ class Interfaces(FactsBase):
data = self.responses[1]
if data:
data = self.parse_addresses(data)
self.populate_ipv4_interfaces(data)
data = self.parse_detail(data)
self.populate_addresses(data, 'ipv4')
data = self.responses[2]
if data:
data = self.parse_addresses(data)
self.populate_ipv6_interfaces(data)
data = self.parse_detail(data)
self.populate_addresses(data, 'ipv6')
data = self.responses[3]
if data:
self.facts['neighbors'] = self.parse_neighbors(data)
self.facts['neighbors'] = list(self.parse_detail(data))
def populate_interfaces(self, data):
for key, value in iteritems(data):
self.facts['interfaces'][key] = value
def populate_ipv4_interfaces(self, data):
for key, value in iteritems(data):
if 'ipv4' not in self.facts['interfaces'][key]:
self.facts['interfaces'][key]['ipv4'] = list()
def populate_addresses(self, data, family):
for value in data:
key = value['interface']
if family not in self.facts['interfaces'][key]:
self.facts['interfaces'][key][family] = list()
addr, subnet = value['address'].split("/")
ipv4 = dict(address=addr.strip(), subnet=subnet.strip())
self.add_ip_address(addr.strip(), 'ipv4')
self.facts['interfaces'][key]['ipv4'].append(ipv4)
def populate_ipv6_interfaces(self, data):
for key, value in iteritems(data):
if key is None:
break
if 'ipv6' not in self.facts['interfaces'][key]:
self.facts['interfaces'][key]['ipv6'] = list()
addr, subnet = value['address'].split("/")
ipv6 = dict(address=addr.strip(), subnet=subnet.strip())
self.add_ip_address(addr.strip(), 'ipv6')
self.facts['interfaces'][key]['ipv6'].append(ipv6)
ip = dict(address=addr.strip(), subnet=subnet.strip())
self.add_ip_address(addr.strip(), family)
self.facts['interfaces'][key][family].append(ip)
def add_ip_address(self, address, family):
if family == 'ipv4':
@ -314,41 +304,19 @@ class Interfaces(FactsBase):
facts = dict()
data = self.preprocess(data)
for line in data:
name = self.parse_name(line)
facts[name] = dict()
for (key, value) in re.findall(self.DETAIL_RE, line):
facts[name][key] = value
parsed = dict(re.findall(self.DETAIL_RE, line))
if "name" not in parsed:
continue
facts[parsed["name"]] = dict(re.findall(self.DETAIL_RE, line))
return facts
def parse_addresses(self, data):
facts = dict()
def parse_detail(self, data):
data = self.preprocess(data)
for line in data:
name = self.parse_interface(line)
facts[name] = dict()
for (key, value) in re.findall(self.DETAIL_RE, line):
facts[name][key] = value
return facts
def parse_neighbors(self, data):
facts = dict()
data = self.preprocess(data)
for line in data:
name = self.parse_interface(line)
facts[name] = dict()
for (key, value) in re.findall(self.DETAIL_RE, line):
facts[name][key] = value
return facts
def parse_name(self, data):
match = re.search(r'name=\"([\w\d\-]+)\"', data, re.M)
if match:
return match.group(1)
def parse_interface(self, data):
match = re.search(r'interface=([\w\d\-]+)', data, re.M)
if match:
return match.group(1)
parsed = dict(re.findall(self.DETAIL_RE, line))
if "interface" not in parsed:
continue
yield parsed
FACT_SUBSETS = dict(

View file

@ -92,7 +92,7 @@ class TestRouterosFactsModule(TestRouterosModule):
set_module_args(dict(gather_subset='interfaces'))
result = self.execute_module()
self.assertIn(
result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0']
result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0', '192.168.88.1']
)
self.assertEqual(
result['ansible_facts']['ansible_net_all_ipv6_addresses'], ['fe80::21c:42ff:fe36:5290']
@ -105,7 +105,7 @@ class TestRouterosFactsModule(TestRouterosModule):
len(result['ansible_facts']['ansible_net_interfaces'].keys()), 11
)
self.assertEqual(
len(result['ansible_facts']['ansible_net_neighbors'].keys()), 4
len(result['ansible_facts']['ansible_net_neighbors']), 4
)
def test_facts_interfaces_no_ipv6(self):
@ -113,7 +113,7 @@ class TestRouterosFactsModule(TestRouterosModule):
'facts/ipv6_address_print_detail_without-paging_no-ipv6'
)
interfaces = self.module.Interfaces(module=self.module)
addresses = interfaces.parse_addresses(data=fixture)
result = interfaces.populate_ipv6_interfaces(data=addresses)
addresses = interfaces.parse_detail(data=fixture)
result = interfaces.populate_addresses(data=addresses, family='ipv6')
self.assertEqual(result, None)