Adds facts processing for ansible_net_neighbors

* Adds logic for consuming ansible_net_neighbors facts

This consumes facts emitted from Ansible over a websocket to
Tower.  This allows consumers in network to process the facts and
emit messges to the network UI.  This requires a special callback
plugin to run in Tower to emit the messages into the websocket using
the python websocket-client library.
This commit is contained in:
Ben Thomasson
2017-08-26 23:18:05 +00:00
parent 6f1000cd94
commit 3f84ef69eb
2 changed files with 108 additions and 64 deletions

View File

@@ -8,7 +8,9 @@ from awx.network_ui.models import DataSheet, DataBinding, DataType
from awx.network_ui.models import Process, Stream from awx.network_ui.models import Process, Stream
from awx.network_ui.models import Toolbox, ToolboxItem from awx.network_ui.models import Toolbox, ToolboxItem
from awx.network_ui.serializers import yaml_serialize_topology from awx.network_ui.serializers import yaml_serialize_topology
from awx.network_ui.messages import MultipleMessage, InterfaceCreate, LinkCreate, to_dict
import urlparse import urlparse
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q from django.db.models import Q
from collections import defaultdict from collections import defaultdict
from django.conf import settings from django.conf import settings
@@ -259,7 +261,8 @@ class _Persistence(object):
print "no sender" print "no sender"
return return
if isinstance(data[1], dict) and client_id != data[1].get('sender'): if isinstance(data[1], dict) and client_id != data[1].get('sender'):
print "client_id mismatch expected:", client_id, "actual:", data[1].get('sender') logger.error("client_id mismatch expected:", client_id, "actual:", data[1].get('sender'))
logger.error(pformat(data))
return return
message_type = data[0] message_type = data[0]
message_value = data[1] message_value = data[1]
@@ -633,75 +636,73 @@ class _Discovery(object):
def onFacts(self, message, topology_id): def onFacts(self, message, topology_id):
send_updates = False send_updates = False
logger.info("onFacts message key %s", message['key']) logger.info("onFacts message key %s", message['key'])
logger.info("onFacts message %s", pformat(message)) #logger.info("onFacts message %s", pformat(message))
return device_name = message['key']
name = message['key'] updates = MultipleMessage('MultipleMessage', [])
device, created = Device.objects.get_or_create(topology_id=topology_id, try:
name=name, device = Device.objects.get(topology_id=topology_id, name=device_name)
defaults=dict(x=0, except ObjectDoesNotExist:
y=0, logger.info("onFacts Could not find %s in topology %s", device_name, topology_id)
type="switch", return
id=0))
if created:
device.id = device.pk
device.save()
send_updates = True
logger.info("onFacts Created device %s", device)
try: try:
interfaces = dpath.util.get(message, '/value/ansible_local/lldp/lldp') interfaces = dpath.util.get(message, '/value/ansible_net_neighbors')
logger.info(pformat(interfaces))
except KeyError: except KeyError:
interfaces = [] interfaces = {}
for interface in interfaces: logger.info("onFacts %s: ", pformat(interfaces))
logger.info("onFacts %s: ", pformat(interface)) """
for inner_interface in interface.get('interface', []): ansible_net_neighbors example:
name = inner_interface.get('name') {u'eth1': [{u'host': u'Spine1', u'port': u'eth3'}],
if not name: u'eth2': [{u'host': u'Spine2', u'port': u'eth3'}],
continue u'eth3': [{u'host': u'Host2', u'port': u'eth1'}]}
interface, created = Interface.objects.get_or_create(device_id=device.pk, """
name=name, for interface_name, neighbors in interfaces.iteritems():
defaults=dict(id=0)) logger.info("interface_name %s neighbors %s", interface_name, neighbors)
if created: interface, created = Interface.objects.get_or_create(device_id=device.pk,
interface.id = interface.pk name=interface_name,
interface.save() defaults=dict(id=0))
send_updates = True if created:
print "Created interface ", interface interface.id = interface.pk
interface.save()
updates.messages.append(InterfaceCreate('InterfaceCreate',
0,
interface.device.id,
interface.id,
interface.name))
send_updates = True
logger.info("Created interface %s", interface)
for neighbor in neighbors:
logger.info("neighbor %s", neighbor)
connected_interface = None connected_interface = None
connected_device = None connected_device = None
neighbor_name = neighbor.get('host')
if not neighbor_name:
continue
try:
connected_device = Device.objects.get(topology_id=topology_id, name=neighbor_name)
except ObjectDoesNotExist:
continue
for chassis in inner_interface.get('chassis', []): logger.info("neighbor %s %s", neighbor_name, connected_device.pk)
name = chassis.get('name', [{}])[0].get('value')
if not name:
continue
connected_device, created = Device.objects.get_or_create(topology_id=topology_id,
name=name,
defaults=dict(x=0,
y=0,
type="switch",
id=0))
if created:
connected_device.id = connected_device.pk
connected_device.save()
send_updates = True
print "Created device ", connected_device
break
if connected_device: remote_interface_name = neighbor.get('port')
for port in inner_interface.get('port', []):
for port_id in port.get('id', []): connected_interface, created = Interface.objects.get_or_create(device_id=connected_device.pk,
if port_id['type'] == 'ifname': name=remote_interface_name,
name = port_id['value'] defaults=dict(id=0))
break if created:
connected_interface, created = Interface.objects.get_or_create(device_id=connected_device.pk, connected_interface.id = connected_interface.pk
name=name, connected_interface.save()
defaults=dict(id=0)) updates.messages.append(InterfaceCreate('InterfaceCreate',
if created: 0,
connected_interface.id = connected_interface.pk connected_interface.device.id,
connected_interface.save() connected_interface.id,
print "Created interface ", connected_interface connected_interface.name))
send_updates = True logger.info("Created interface %s", connected_interface)
send_updates = True
if connected_device and connected_interface: if connected_device and connected_interface:
exists = Link.objects.filter(Q(from_device_id=device.pk, exists = Link.objects.filter(Q(from_device_id=device.pk,
@@ -718,14 +719,25 @@ class _Discovery(object):
from_interface_id=interface.pk, from_interface_id=interface.pk,
to_interface_id=connected_interface.pk, to_interface_id=connected_interface.pk,
id=0) id=0)
link.save() link.save()
link.id = link.pk link.id = link.pk
link.save() link.save()
print "Created link ", link updates.messages.append(LinkCreate('LinkCreate',
0,
link.id,
link.name,
link.from_device.id,
link.to_device.id,
link.from_interface.id,
link.to_interface.id))
logger.info("Created link %s", link)
send_updates = True send_updates = True
if send_updates: if send_updates:
send_snapshot(Group("topology-%s" % topology_id), topology_id) logger.info("onFacts send_updates")
channel = Group("topology-%s" % topology_id)
channel.send({"text": json.dumps([updates.msg_type, to_dict(updates)])})
discovery = _Discovery() discovery = _Discovery()

View File

@@ -0,0 +1,32 @@
from collections import namedtuple
MultipleMessage = namedtuple('MultipleMessage', ['msg_type',
'messages'])
InterfaceCreate = namedtuple('InterfaceCreate', ['msg_type',
'sender',
'device_id',
'id',
'name'])
LinkCreate = namedtuple('LinkCreate', ['msg_type',
'sender',
'id',
'name',
'from_device_id',
'to_device_id',
'from_interface_id',
'to_interface_id'])
def to_dict(message):
if isinstance(message, MultipleMessage):
d = dict(message._asdict())
inner_messages = []
for m in d['messages']:
inner_messages.append(to_dict(m))
d['messages'] = inner_messages
return d
else:
return dict(message._asdict())