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

View file

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