From 3f84ef69ebc9b94f040c42d83a80bbe64741078e Mon Sep 17 00:00:00 2001 From: Ben Thomasson Date: Sat, 26 Aug 2017 23:18:05 +0000 Subject: [PATCH] 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. --- awx/network_ui/consumers.py | 140 +++++++++++++++++++----------------- awx/network_ui/messages.py | 32 +++++++++ 2 files changed, 108 insertions(+), 64 deletions(-) create mode 100644 awx/network_ui/messages.py diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py index cb1e1ed949..ad3ac6911c 100644 --- a/awx/network_ui/consumers.py +++ b/awx/network_ui/consumers.py @@ -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 Toolbox, ToolboxItem from awx.network_ui.serializers import yaml_serialize_topology +from awx.network_ui.messages import MultipleMessage, InterfaceCreate, LinkCreate, to_dict import urlparse +from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from collections import defaultdict from django.conf import settings @@ -259,7 +261,8 @@ class _Persistence(object): print "no sender" return 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 message_type = data[0] message_value = data[1] @@ -633,75 +636,73 @@ class _Discovery(object): def onFacts(self, message, topology_id): send_updates = False logger.info("onFacts message key %s", message['key']) - logger.info("onFacts message %s", pformat(message)) - return - name = message['key'] - 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: - device.id = device.pk - device.save() - send_updates = True - logger.info("onFacts Created device %s", device) + #logger.info("onFacts message %s", pformat(message)) + device_name = message['key'] + updates = MultipleMessage('MultipleMessage', []) + try: + device = Device.objects.get(topology_id=topology_id, name=device_name) + except ObjectDoesNotExist: + logger.info("onFacts Could not find %s in topology %s", device_name, topology_id) + return 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: - interfaces = [] - for interface in interfaces: - logger.info("onFacts %s: ", pformat(interface)) - for inner_interface in interface.get('interface', []): - name = inner_interface.get('name') - if not name: - continue - interface, created = Interface.objects.get_or_create(device_id=device.pk, - name=name, - defaults=dict(id=0)) - if created: - interface.id = interface.pk - interface.save() - send_updates = True - print "Created interface ", interface + interfaces = {} + logger.info("onFacts %s: ", pformat(interfaces)) + """ + ansible_net_neighbors example: + {u'eth1': [{u'host': u'Spine1', u'port': u'eth3'}], + u'eth2': [{u'host': u'Spine2', u'port': u'eth3'}], + u'eth3': [{u'host': u'Host2', u'port': u'eth1'}]} + """ + for interface_name, neighbors in interfaces.iteritems(): + logger.info("interface_name %s neighbors %s", interface_name, neighbors) + interface, created = Interface.objects.get_or_create(device_id=device.pk, + name=interface_name, + defaults=dict(id=0)) + if created: + 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_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', []): - 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 + logger.info("neighbor %s %s", neighbor_name, connected_device.pk) - if connected_device: - for port in inner_interface.get('port', []): - for port_id in port.get('id', []): - if port_id['type'] == 'ifname': - name = port_id['value'] - break - connected_interface, created = Interface.objects.get_or_create(device_id=connected_device.pk, - name=name, - defaults=dict(id=0)) - if created: - connected_interface.id = connected_interface.pk - connected_interface.save() - print "Created interface ", connected_interface - send_updates = True + remote_interface_name = neighbor.get('port') + + connected_interface, created = Interface.objects.get_or_create(device_id=connected_device.pk, + name=remote_interface_name, + defaults=dict(id=0)) + if created: + connected_interface.id = connected_interface.pk + connected_interface.save() + updates.messages.append(InterfaceCreate('InterfaceCreate', + 0, + connected_interface.device.id, + connected_interface.id, + connected_interface.name)) + logger.info("Created interface %s", connected_interface) + send_updates = True if connected_device and connected_interface: exists = Link.objects.filter(Q(from_device_id=device.pk, @@ -718,14 +719,25 @@ class _Discovery(object): from_interface_id=interface.pk, to_interface_id=connected_interface.pk, id=0) + link.save() link.id = link.pk 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 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() diff --git a/awx/network_ui/messages.py b/awx/network_ui/messages.py new file mode 100644 index 0000000000..8723e16014 --- /dev/null +++ b/awx/network_ui/messages.py @@ -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())