diff --git a/Makefile b/Makefile index 5151046635..54d0c49be3 100644 --- a/Makefile +++ b/Makefile @@ -374,7 +374,7 @@ awx-link: sed -i "s/placeholder/$(shell git describe --long | sed 's/\./\\./g')/" /awx_devel/awx.egg-info/PKG-INFO cp /tmp/awx.egg-link /venv/awx/lib/python2.7/site-packages/awx.egg-link -TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests awx/network_ui/tests/unit +TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests # Run all API unit tests. test: @@ -389,7 +389,7 @@ test_unit: @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi; \ - py.test awx/main/tests/unit awx/conf/tests/unit awx/sso/tests/unit awx/network_ui/tests/unit + py.test awx/main/tests/unit awx/conf/tests/unit awx/sso/tests/unit test_ansible: @if [ "$(VENV_BASE)" ]; then \ diff --git a/awx/main/routing.py b/awx/main/routing.py index 79a3c84a5a..0a49f25c6c 100644 --- a/awx/main/routing.py +++ b/awx/main/routing.py @@ -1,5 +1,4 @@ from channels.routing import route -from awx.network_ui.routing import channel_routing as network_ui_routing channel_routing = [ @@ -7,6 +6,3 @@ channel_routing = [ route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'), route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'), ] - - -channel_routing += network_ui_routing diff --git a/awx/network_ui/CONTRIBUTING.md b/awx/network_ui/CONTRIBUTING.md deleted file mode 100644 index 106e5df02d..0000000000 --- a/awx/network_ui/CONTRIBUTING.md +++ /dev/null @@ -1,132 +0,0 @@ -Network UI -========== - -See [awx/ui/client/src/network-ui/CONTRIBUTING.md](../ui/client/src/network-ui/CONTRIBUTING.md) for the introduction -to the Network UI client-side development. - -Server-Side Development ------------------------ - -This document covers the Network UI server-side development. - -The Network UI is a UX driven feature to provide a graphical user -experience that fits well into the network engineer's normal workflow. Their -normal workflow includes a diagram drawn in a graphical drawing program, a -spreadsheet, and the command line interface of their network gear. Network -architects design the network on the graphical diagram and then hand off the -architecture to network operators who implement the architecture on the network -using spreadsheets to manage their data and manually converting the data into -CLI commands using their networking expertise and expertise with their physical -gear. - -The server-side code supports the persistence needed to provide this graphical -user experience of architecting a network and using that information along with -additional information (stored in vars files) to configure the network devices -using the CLI or NETCONF using Ansible playbooks and roles. - -Network UI Data Schema ----------------------- - -For the 3.3 release the persistence needed includes the position information of -the devices on the virtual canvas and the type of the devices as well as -information about the interfaces on the devices and the links connecting those -interfaces. - -These requirements determine the database schema needed for the network UI which -requires these models: Topology, Device, Interface, Link, and TopologyInventory. - - - -This diagram shows the relationships between the models in the Network UI schema. - -The models are: - -* Device - a host, switch, router, or other networking device -* Interface - a connection point on a device for a link -* Link - a physical connection between two devices to their respective interfaces -* Topology - a collection of devices and links -* TopologyInventory - a mapping between topologies and Tower inventories - - -Network UI Websocket Protocol ------------------------------ - -Persistence for the network UI canvas state is implemented using an -asynchronous websocket protocol to send information from the client to the -server and vice-versa. This two-way communication was chosen to support future -features for streaming data to the canvas, broadcast messaging between clients, -and for interaction performance on the UI. - - -Messages --------- - -JSON messages are passed over the `/network_ui/topology` websocket between the -test client and the test server. The protocol that is used for all messages is -in ABNF (RFC5234): - - - message_type = 'DeviceMove' / 'DeviceCreate' / 'DeviceDestroy' / 'DeviceLabelEdit' / 'DeviceSelected' / 'DeviceUnSelected' / 'InterfaceCreate' / 'InterfaceLabelEdit' / 'LinkLabelEdit' / 'LinkCreate' / 'LinkDestroy' / 'LinkSelected' / 'LinkUnSelected' / 'MultipleMessage' / 'Snapshot' - message_data = '{' 'msg_type' ': ' message_type ', ' key-value *( ', ' key-value ) '}' - message = '[ id , ' posint ']' / '[ topology_id , ' posint ']' / '[' message_type ', ' message_data ']' - -See https://github.com/AndyA/abnfgen/blob/master/andy/json.abnf for the rest of -the JSON ABNF. - -See [designs/messages.yml](designs/messages.yml) for the allowable keys and -values for each message type. - - -Initially when the websocket is first opened the server will send four messages -to the client. These are: - -* the client id using the `id` message type. -* the topology id using the `topology` message type. -* a Topology record containing data for the canvas itself. -* a Snapshot message containing all the data of the data on the canvas. - -As the user interacts with the canvas messages will be generated by the client -and the `network_ui.consumers.Persistence` class will update the models that -represent the canvas. - - - -Persistence ------------ - -The class `awx.network_uiconsumers.Persistence` provides persistence for the Network UI canvas. -It does so by providing message handlers that handle storage of the canvas change events -into the database. Each event has a message handle with name `onX` where `X` is the name of the message -type. The handlers use the `filter/values_list`, `filter/values`, `filter/update`, and `filter/delete` -patterns to update the data in the database quickly with a constant O(1) number of queries per event -often with only one query needed. With `filter/update` and `filter/delete` all the work is done -in the database and Python never needs to instaniate and garbage collect the model objects. - -Bulk operations (`filter/values`) in `send_snapshot` are used to produce a constant number of -queries produce a snapshot when the canvas is first loaded. This method avoids creating -the model objects since it only produces dicts that are JSON serializable which are bundled -together for the `Snapshot` message type. - -This method of persistence uses Django as a database query-compiler for transforms from -the event types to the database types. Using Django in this way is very performant since -Python does very little work processing the data and when possible the data never leaves -the database. - - -Client Tracking ---------------- - -Each user session to the network UI canvas is tracked with the `client_id` param. Multiple -clients can view and interact with the network UI canvas at a time. They will see each other's -edits to the canvas in real time. This works by broadcasting the canvas change events to -all clients viewing the same topology. - -``` - # Send to all clients editing the topology - Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']}) -``` - -API ---- - -There is no user accessible API for this feature in the 3.3 release. diff --git a/awx/network_ui/__init__.py b/awx/network_ui/__init__.py deleted file mode 100644 index ebed9407c5..0000000000 --- a/awx/network_ui/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc - diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py deleted file mode 100644 index a62fe73cdb..0000000000 --- a/awx/network_ui/consumers.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc -import channels -from channels.auth import channel_session_user, channel_session_user_from_http -from awx.network_ui.models import Topology, Device, Link, Interface -from awx.network_ui.models import TopologyInventory -from awx.main.models.inventory import Inventory -import urlparse -from django.db.models import Q -from collections import defaultdict -import logging -import uuid -import six - - -from awx.network_ui.utils import transform_dict - -import json - -logger = logging.getLogger("awx.network_ui.consumers") - - -def parse_inventory_id(data): - inventory_id = data.get('inventory_id', ['null']) - try: - inventory_id = int(inventory_id[0]) - except ValueError: - inventory_id = None - except IndexError: - inventory_id = None - except TypeError: - inventory_id = None - if not inventory_id: - inventory_id = None - return inventory_id - - -class NetworkingEvents(object): - - ''' - Provides handlers for the networking events for the topology canvas. - ''' - - def parse_message_text(self, message_text, client_id): - ''' - See the Messages of CONTRIBUTING.md for the message format. - ''' - data = json.loads(message_text) - if len(data) == 2: - message_type = data.pop(0) - message_value = data.pop(0) - if isinstance(message_value, list): - logger.warning("Message has no sender") - return None, None - if isinstance(message_value, dict) and client_id != message_value.get('sender'): - logger.warning("client_id mismatch expected: %s actual %s", client_id, message_value.get('sender')) - return None, None - return message_type, message_value - else: - logger.error("Invalid message text") - return None, None - - def handle(self, message): - ''' - Dispatches a message based on the message type to a handler function - of name onX where X is the message type. - ''' - topology_id = message.get('topology') - if topology_id is None: - logger.warning("Unsupported message %s: no topology", message) - return - client_id = message.get('client') - if client_id is None: - logger.warning("Unsupported message %s: no client", message) - return - if not message.get('can_edit'): - logger.warning("Client {0} does not have permission to edit topology {1}".format(client_id, topology_id)) - return - if 'text' not in message: - logger.warning("Unsupported message %s: no data", message) - return - message_type, message_value = self.parse_message_text(message['text'], client_id) - if message_type is None: - logger.warning("Unsupported message %s: no message type", message) - return - handler = self.get_handler(message_type) - if handler is not None: - handler(message_value, topology_id, client_id) - else: - logger.warning("Unsupported message %s: no handler", message_type) - - def get_handler(self, message_type): - return getattr(self, "on{0}".format(message_type), None) - - def onDeviceCreate(self, device, topology_id, client_id): - device = transform_dict(dict(x='x', - y='y', - name='name', - type='device_type', - id='cid', - host_id='host_id'), device) - logger.info("Device created %s", device) - d, _ = Device.objects.get_or_create(topology_id=topology_id, cid=device['cid'], defaults=device) - d.x = device['x'] - d.y = device['y'] - d.device_type = device['device_type'] - d.host_id = device['host_id'] - d.save() - (Topology.objects - .filter(pk=topology_id, device_id_seq__lt=device['cid']) - .update(device_id_seq=device['cid'])) - - def onDeviceDestroy(self, device, topology_id, client_id): - logger.info("Device removed %s", device) - Device.objects.filter(topology_id=topology_id, cid=device['id']).delete() - - def onDeviceMove(self, device, topology_id, client_id): - Device.objects.filter(topology_id=topology_id, cid=device['id']).update(x=device['x'], y=device['y']) - - def onDeviceLabelEdit(self, device, topology_id, client_id): - logger.debug("Device label edited %s", device) - Device.objects.filter(topology_id=topology_id, cid=device['id']).update(name=device['name']) - - def onInterfaceLabelEdit(self, interface, topology_id, client_id): - (Interface.objects - .filter(device__topology_id=topology_id, - cid=interface['id'], - device__cid=interface['device_id']) - .update(name=interface['name'])) - - def onLinkLabelEdit(self, link, topology_id, client_id): - logger.debug("Link label edited %s", link) - Link.objects.filter(from_device__topology_id=topology_id, cid=link['id']).update(name=link['name']) - - def onInterfaceCreate(self, interface, topology_id, client_id): - Interface.objects.get_or_create(device_id=Device.objects.get(cid=interface['device_id'], - topology_id=topology_id).pk, - cid=interface['id'], - defaults=dict(name=interface['name'])) - (Device.objects - .filter(cid=interface['device_id'], - topology_id=topology_id, - interface_id_seq__lt=interface['id']) - .update(interface_id_seq=interface['id'])) - - def onLinkCreate(self, link, topology_id, client_id): - logger.debug("Link created %s", link) - device_map = dict(Device.objects - .filter(topology_id=topology_id, cid__in=[link['from_device_id'], link['to_device_id']]) - .values_list('cid', 'pk')) - if link['from_device_id'] not in device_map: - logger.warning('Device not found') - return - if link['to_device_id'] not in device_map: - logger.warning('Device not found') - return - Link.objects.get_or_create(cid=link['id'], - name=link['name'], - from_device_id=device_map[link['from_device_id']], - to_device_id=device_map[link['to_device_id']], - from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']], - cid=link['from_interface_id']).pk, - to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']], - cid=link['to_interface_id']).pk) - (Topology.objects - .filter(pk=topology_id, link_id_seq__lt=link['id']) - .update(link_id_seq=link['id'])) - - def onLinkDestroy(self, link, topology_id, client_id): - logger.debug("Link deleted %s", link) - device_map = dict(Device.objects - .filter(topology_id=topology_id, cid__in=[link['from_device_id'], link['to_device_id']]) - .values_list('cid', 'pk')) - if link['from_device_id'] not in device_map: - logger.warning('Device not found') - return - if link['to_device_id'] not in device_map: - logger.warning('Device not found') - return - Link.objects.filter(cid=link['id'], - from_device_id=device_map[link['from_device_id']], - to_device_id=device_map[link['to_device_id']], - from_interface_id=Interface.objects.get(device_id=device_map[link['from_device_id']], - cid=link['from_interface_id']).pk, - to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']], - cid=link['to_interface_id']).pk).delete() - - def onDeviceSelected(self, message_value, topology_id, client_id): - 'Ignore DeviceSelected messages' - pass - - def onDeviceUnSelected(self, message_value, topology_id, client_id): - 'Ignore DeviceSelected messages' - pass - - def onLinkSelected(self, message_value, topology_id, client_id): - 'Ignore LinkSelected messages' - pass - - def onLinkUnSelected(self, message_value, topology_id, client_id): - 'Ignore LinkSelected messages' - pass - - def onMultipleMessage(self, message_value, topology_id, client_id): - for message in message_value['messages']: - handler = self.get_handler(message['msg_type']) - if handler is not None: - handler(message, topology_id, client_id) - else: - logger.warning("Unsupported message %s", message['msg_type']) - - -networking_events_dispatcher = NetworkingEvents() - - -@channel_session_user_from_http -def ws_connect(message): - if not message.user.is_authenticated(): - logger.error("Request user is not authenticated to use websocket.") - message.reply_channel.send({"close": True}) - return - else: - message.reply_channel.send({"accept": True}) - - data = urlparse.parse_qs(message.content['query_string']) - inventory_id = parse_inventory_id(data) - try: - inventory = Inventory.objects.get(id=inventory_id) - except Inventory.DoesNotExist: - logger.error("User {} attempted connecting inventory_id {} that does not exist.".format( - message.user.id, inventory_id) - ) - message.reply_channel.send({"close": True}) - return - if message.user not in inventory.read_role: - logger.warn("User {} attempted connecting to inventory_id {} without permission.".format( - message.user.id, inventory_id - )) - message.reply_channel.send({"close": True}) - return - message.channel_session['can_edit'] = message.user in inventory.admin_role - topology_ids = list(TopologyInventory.objects.filter(inventory_id=inventory_id).values_list('pk', flat=True)) - topology_id = None - if len(topology_ids) > 0: - topology_id = topology_ids[0] - if topology_id is not None: - topology = Topology.objects.get(pk=topology_id) - else: - topology = Topology(name="topology", scale=0.7, panX=0, panY=0) - topology.save() - TopologyInventory(inventory_id=inventory_id, topology_id=topology.pk).save() - topology_id = topology.pk - message.channel_session['topology_id'] = topology_id - channels.Group("topology-%s" % topology_id).add(message.reply_channel) - client_id = six.text_type(uuid.uuid4()) - message.channel_session['client_id'] = client_id - channels.Group("client-%s" % client_id).add(message.reply_channel) - message.reply_channel.send({"text": json.dumps(["id", client_id])}) - message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])}) - topology_data = transform_dict(dict(id='topology_id', - name='name', - panX='panX', - panY='panY', - scale='scale', - link_id_seq='link_id_seq', - device_id_seq='device_id_seq'), topology.__dict__) - - message.reply_channel.send({"text": json.dumps(["Topology", topology_data])}) - send_snapshot(message.reply_channel, topology_id) - - -def send_snapshot(channel, topology_id): - interfaces = defaultdict(list) - - for i in (Interface.objects - .filter(device__topology_id=topology_id) - .values()): - i = transform_dict(dict(cid='id', - device_id='device_id', - id='interface_id', - name='name'), i) - interfaces[i['device_id']].append(i) - devices = list(Device.objects.filter(topology_id=topology_id).values()) - devices = [transform_dict(dict(cid='id', - id='device_id', - device_type='device_type', - host_id='host_id', - name='name', - x='x', - y='y', - interface_id_seq='interface_id_seq'), x) for x in devices] - for device in devices: - device['interfaces'] = interfaces[device['device_id']] - - links = [dict(id=x['cid'], - name=x['name'], - from_device_id=x['from_device__cid'], - to_device_id=x['to_device__cid'], - from_interface_id=x['from_interface__cid'], - to_interface_id=x['to_interface__cid']) - for x in list(Link.objects - .filter(Q(from_device__topology_id=topology_id) | - Q(to_device__topology_id=topology_id)) - .values('cid', - 'name', - 'from_device__cid', - 'to_device__cid', - 'from_interface__cid', - 'to_interface__cid'))] - snapshot = dict(sender=0, - devices=devices, - links=links) - channel.send({"text": json.dumps(["Snapshot", snapshot])}) - - -@channel_session_user -def ws_message(message): - # Send to all clients editing the topology - if message.channel_session['can_edit']: - channels.Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']}) - # Send to networking_events handler - networking_events_dispatcher.handle({"text": message['text'], - "topology": message.channel_session['topology_id'], - "client": message.channel_session['client_id'], - "can_edit": message.channel_session['can_edit']}) - - -@channel_session_user -def ws_disconnect(message): - if 'topology_id' in message.channel_session: - channels.Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel) diff --git a/awx/network_ui/docs/README.md b/awx/network_ui/docs/README.md deleted file mode 100644 index 2c8ff94bda..0000000000 --- a/awx/network_ui/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ - - -The design files in this directory are used in the database schema designer tool. - -* [models.png](models.png) - An image of the database schema design for network UI. -* [models.yml](models.yml) - Provides the main schema design for the network UI project. - - diff --git a/awx/network_ui/docs/messages.yml b/awx/network_ui/docs/messages.yml deleted file mode 100644 index 06ee3b9b75..0000000000 --- a/awx/network_ui/docs/messages.yml +++ /dev/null @@ -1,19 +0,0 @@ -messages: - - {msg_type: DeviceMove, fields: [msg_type, sender, id, x, y, previous_x, previous_y]} - - {msg_type: DeviceCreate, fields: [msg_type, sender, id, x, y, name, type, host_id]} - - {msg_type: DeviceDestroy, fields: [msg_type, sender, id, previous_x, previous_y, previous_name, previous_type, previous_host_id]} - - {msg_type: DeviceLabelEdit, fields: [msg_type, sender, id, name, previous_name]} - - {msg_type: DeviceSelected, fields: [msg_type, sender, id]} - - {msg_type: DeviceUnSelected, fields: [msg_type, sender, id]} - - {msg_type: InterfaceCreate, fields: [msg_type, sender, device_id, id, name]} - - {msg_type: InterfaceLabelEdit, fields: [msg_type, sender, id, device_id, name, previous_name]} - - {msg_type: LinkLabelEdit, fields: [msg_type, sender, id, name, previous_name]} - - {msg_type: LinkCreate, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]} - - {msg_type: LinkDestroy, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]} - - {msg_type: LinkSelected, fields: [msg_type, sender, id]} - - {msg_type: LinkUnSelected, fields: [msg_type, sender, id]} - - {msg_type: MultipleMessage, fields: [msg_type, sender, messages]} - - {msg_type: Snapshot, fields: [msg_type, sender, devices, links, order, trace_id]} - - {msg_type: id, type: int} - - {msg_type: topology_id, type: int} - - {msg_type: Topology, fields: [topology_id, name, panX, panY, scale, link_id_seq, device_id_seq]} diff --git a/awx/network_ui/docs/models.png b/awx/network_ui/docs/models.png deleted file mode 100644 index c6b22910d8..0000000000 Binary files a/awx/network_ui/docs/models.png and /dev/null differ diff --git a/awx/network_ui/docs/models.yml b/awx/network_ui/docs/models.yml deleted file mode 100644 index 3176306249..0000000000 --- a/awx/network_ui/docs/models.yml +++ /dev/null @@ -1,123 +0,0 @@ -app: awx.network_ui -external_models: [] -models: -- display: name - fields: - - name: device_id - pk: true - type: AutoField - - name: topology - ref: Topology - ref_field: topology_id - type: ForeignKey - - len: 200 - name: name - type: CharField - - name: x - type: IntegerField - - name: y - type: IntegerField - - name: id - type: IntegerField - - len: 200 - name: device_type - type: CharField - - default: 0 - name: interface_id_seq - type: IntegerField - - default: 0 - name: host_id - type: IntegerField - name: Device - x: 348 - y: 124 -- fields: - - name: link_id - pk: true - type: AutoField - - name: from_device - ref: Device - ref_field: device_id - related_name: from_link - type: ForeignKey - - name: to_device - ref: Device - ref_field: device_id - related_name: to_link - type: ForeignKey - - name: from_interface - ref: Interface - ref_field: interface_id - related_name: from_link - type: ForeignKey - - name: to_interface - ref: Interface - ref_field: interface_id - related_name: to_link - type: ForeignKey - - name: id - type: IntegerField - - len: 200 - name: name - type: CharField - name: Link - x: 731 - y: -33 -- display: name - fields: - - name: topology_id - pk: true - type: AutoField - - len: 200 - name: name - type: CharField - - name: scale - type: FloatField - - name: panX - type: FloatField - - name: panY - type: FloatField - - default: 0 - name: device_id_seq - type: IntegerField - - default: 0 - name: link_id_seq - type: IntegerField - name: Topology - x: 111 - y: 127 -- display: name - fields: - - name: interface_id - pk: true - type: AutoField - - name: device - ref: Device - ref_field: device_id - type: ForeignKey - - len: 200 - name: name - type: CharField - - name: id - type: IntegerField - name: Interface - x: 977 - y: 312 -- fields: - - name: topology_inventory_id - pk: true - type: AutoField - - name: topology - ref: Topology - ref_field: topology_id - type: ForeignKey - - name: inventory_id - type: IntegerField - name: TopologyInventory - x: -204 - y: 12 -modules: [] -view: - panX: 213.729555519212 - panY: 189.446959094643 - scaleXY: 0.69 diff --git a/awx/network_ui/migrations/0001_initial.py b/awx/network_ui/migrations/0001_initial.py deleted file mode 100644 index 07013104e1..0000000000 --- a/awx/network_ui/migrations/0001_initial.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.11 on 2018-03-23 20:43 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('main', '0027_v330_emitted_events'), - ] - - operations = [ - migrations.CreateModel( - name='Client', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ], - ), - migrations.CreateModel( - name='Device', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=200)), - ('x', models.IntegerField()), - ('y', models.IntegerField()), - ('cid', models.IntegerField()), - ('device_type', models.CharField(blank=True, max_length=200)), - ('interface_id_seq', models.IntegerField(default=0)), - ('host', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Host')), - ], - ), - migrations.CreateModel( - name='Interface', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=200)), - ('cid', models.IntegerField()), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Device')), - ], - ), - migrations.CreateModel( - name='Link', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('cid', models.IntegerField()), - ('name', models.CharField(blank=True, max_length=200)), - ('from_device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_link', to='network_ui.Device')), - ('from_interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_link', to='network_ui.Interface')), - ('to_device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_link', to='network_ui.Device')), - ('to_interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_link', to='network_ui.Interface')), - ], - ), - migrations.CreateModel( - name='Topology', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=200)), - ('scale', models.FloatField()), - ('panX', models.FloatField()), - ('panY', models.FloatField()), - ('device_id_seq', models.IntegerField(default=0)), - ('link_id_seq', models.IntegerField(default=0)), - ], - ), - migrations.CreateModel( - name='TopologyInventory', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('inventory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Inventory')), - ('topology', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Topology')), - ], - ), - migrations.AddField( - model_name='device', - name='topology', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Topology'), - ), - ] diff --git a/awx/network_ui/migrations/0002_delete_client.py b/awx/network_ui/migrations/0002_delete_client.py deleted file mode 100644 index fe5708fa3c..0000000000 --- a/awx/network_ui/migrations/0002_delete_client.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.11 on 2018-05-30 17:18 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('network_ui', '0001_initial'), - ] - - operations = [ - migrations.DeleteModel( - name='Client', - ), - ] diff --git a/awx/network_ui/migrations/__init__.py b/awx/network_ui/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/awx/network_ui/models.py b/awx/network_ui/models.py deleted file mode 100644 index 9712a6ce00..0000000000 --- a/awx/network_ui/models.py +++ /dev/null @@ -1,60 +0,0 @@ -from django.db import models - - -class Device(models.Model): - - id = models.AutoField(primary_key=True,) - topology = models.ForeignKey('Topology',) - name = models.CharField(max_length=200, blank=True) - x = models.IntegerField() - y = models.IntegerField() - cid = models.IntegerField() - device_type = models.CharField(max_length=200, blank=True) - interface_id_seq = models.IntegerField(default=0,) - host = models.ForeignKey('main.Host', default=None, null=True, on_delete=models.SET_NULL) - - def __unicode__(self): - return self.name - - -class Link(models.Model): - - id = models.AutoField(primary_key=True,) - from_device = models.ForeignKey('Device', related_name='from_link',) - to_device = models.ForeignKey('Device', related_name='to_link',) - from_interface = models.ForeignKey('Interface', related_name='from_link',) - to_interface = models.ForeignKey('Interface', related_name='to_link',) - cid = models.IntegerField() - name = models.CharField(max_length=200, blank=True) - - -class Topology(models.Model): - - id = models.AutoField(primary_key=True,) - name = models.CharField(max_length=200, blank=True) - scale = models.FloatField() - panX = models.FloatField() - panY = models.FloatField() - device_id_seq = models.IntegerField(default=0,) - link_id_seq = models.IntegerField(default=0,) - - def __unicode__(self): - return self.name - - -class Interface(models.Model): - - id = models.AutoField(primary_key=True,) - device = models.ForeignKey('Device',) - name = models.CharField(max_length=200, blank=True) - cid = models.IntegerField() - - def __unicode__(self): - return self.name - - -class TopologyInventory(models.Model): - - id = models.AutoField(primary_key=True,) - topology = models.ForeignKey('Topology',) - inventory = models.ForeignKey('main.Inventory') diff --git a/awx/network_ui/routing.py b/awx/network_ui/routing.py deleted file mode 100644 index 0a9d07635d..0000000000 --- a/awx/network_ui/routing.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc -from channels.routing import route -from awx.network_ui.consumers import ws_connect, ws_message, ws_disconnect - -channel_routing = [ - route("websocket.connect", ws_connect, path=r"^/network_ui/topology/"), - route("websocket.receive", ws_message, path=r"^/network_ui/topology/"), - route("websocket.disconnect", ws_disconnect, path=r"^/network_ui/topology/"), -] diff --git a/awx/network_ui/tests/__init__.py b/awx/network_ui/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/awx/network_ui/tests/conftest.py b/awx/network_ui/tests/conftest.py deleted file mode 100644 index ca4f2f1cda..0000000000 --- a/awx/network_ui/tests/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest -from mock import PropertyMock - - -@pytest.fixture(autouse=True) -def _disable_database_settings(mocker): - m = mocker.patch('awx.conf.settings.SettingsWrapper.all_supported_settings', new_callable=PropertyMock) - m.return_value = [] - diff --git a/awx/network_ui/tests/functional/__init__.py b/awx/network_ui/tests/functional/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/awx/network_ui/tests/functional/test_consumers.py b/awx/network_ui/tests/functional/test_consumers.py deleted file mode 100644 index 3b6c0c38d3..0000000000 --- a/awx/network_ui/tests/functional/test_consumers.py +++ /dev/null @@ -1,246 +0,0 @@ -import mock -import logging -import json -import imp -from mock import patch -patch('channels.auth.channel_session_user', lambda x: x).start() -patch('channels.auth.channel_session_user_from_http', lambda x: x).start() - -from awx.main.models import Inventory # noqa -from awx.network_ui.consumers import parse_inventory_id, networking_events_dispatcher, send_snapshot # noqa -from awx.network_ui.models import Topology, Device, Link, Interface, TopologyInventory # noqa -import awx # noqa -import awx.network_ui # noqa -import awx.network_ui.consumers # noqa -imp.reload(awx.network_ui.consumers) - - -def test_parse_inventory_id(): - assert parse_inventory_id({}) is None - assert parse_inventory_id({'inventory_id': ['1']}) == 1 - assert parse_inventory_id({'inventory_id': ['0']}) is None - assert parse_inventory_id({'inventory_id': ['X']}) is None - assert parse_inventory_id({'inventory_id': []}) is None - assert parse_inventory_id({'inventory_id': 'x'}) is None - assert parse_inventory_id({'inventory_id': '12345'}) == 1 - assert parse_inventory_id({'inventory_id': 1}) is None - - -def test_network_events_handle_message_incomplete_message1(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle({}) - log_mock.assert_called_once_with( - 'Unsupported message %s: no topology', {}) - - -def test_network_events_handle_message_incomplete_message2(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle({'topology': [0]}) - log_mock.assert_called_once_with( - 'Unsupported message %s: no client', {'topology': [0]}) - - -def test_network_events_handle_message_incomplete_message3(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle({'topology': [1]}) - log_mock.assert_called_once_with( - 'Unsupported message %s: no client', {'topology': [1]}) - - -def test_network_events_handle_message_incomplete_message4(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle({'topology': 1, 'client': 1}) - log_mock.assert_called_once_with('Unsupported message %s: no data', { - 'client': 1, 'topology': 1}) - - -def test_network_events_handle_message_incomplete_message5(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - message = ['DeviceCreate'] - networking_events_dispatcher.handle( - {'topology': 1, 'client': 1, 'text': json.dumps(message)}) - log_mock.assert_called_once_with('Unsupported message %s: no message type', { - 'text': '["DeviceCreate"]', 'client': 1, 'topology': 1}) - - -def test_network_events_handle_message_incomplete_message6(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - message = ['DeviceCreate', []] - networking_events_dispatcher.handle( - {'topology': 1, 'client': 1, 'text': json.dumps(message)}) - log_mock.assert_has_calls([ - mock.call('Message has no sender'), - mock.call('Unsupported message %s: no message type', {'text': '["DeviceCreate", []]', 'client': 1, 'topology': 1})]) - - -def test_network_events_handle_message_incomplete_message7(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - message = ['DeviceCreate', {}] - networking_events_dispatcher.handle( - {'topology': 1, 'client': 1, 'text': json.dumps(message)}) - log_mock.assert_has_calls([ - mock.call('client_id mismatch expected: %s actual %s', 1, None), - mock.call('Unsupported message %s: no message type', {'text': '["DeviceCreate", {}]', 'client': 1, 'topology': 1})]) - - -def test_network_events_handle_message_incomplete_message8(): - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock: - message = ['Unsupported', {'sender': 1}] - networking_events_dispatcher.handle( - {'topology': 1, 'client': 1, 'text': json.dumps(message)}) - log_mock.assert_called_once_with( - 'Unsupported message %s: no handler', u'Unsupported') - - -def test_send_snapshot_empty(): - channel = mock.MagicMock() - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects'),\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects'),\ - mock.patch.object(Topology, 'objects'): - send_snapshot(channel, 1) - log_mock.assert_not_called() - channel.send.assert_called_once_with( - {'text': '["Snapshot", {"links": [], "devices": [], "sender": 0}]'}) - - -def test_send_snapshot_single(): - channel = mock.MagicMock() - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects') as interface_objects_mock: - - interface_objects_mock.filter.return_value.values.return_value = [ - dict(cid=1, device_id=1, id=1, name="eth0")] - device_objects_mock.filter.return_value.values.return_value = [ - dict(cid=1, id=1, device_type="host", name="host1", x=0, y=0, - interface_id_seq=1, host_id=1)] - send_snapshot(channel, 1) - device_objects_mock.filter.assert_called_once_with(topology_id=1) - device_objects_mock.filter.return_value.values.assert_called_once_with() - interface_objects_mock.filter.assert_called_once_with( - device__topology_id=1) - interface_objects_mock.filter.return_value.values.assert_called_once_with() - log_mock.assert_not_called() - channel.send.assert_called_once_with( - {'text': '''["Snapshot", {"links": [], "devices": [{"interface_id_seq": 1, \ -"name": "host1", "interfaces": [{"id": 1, "device_id": 1, "name": "eth0", "interface_id": 1}], \ -"device_type": "host", "host_id": 1, "y": 0, "x": 0, "id": 1, "device_id": 1}], "sender": 0}]'''}) - - -def test_ws_disconnect(): - message = mock.MagicMock() - message.channel_session = dict(topology_id=1) - message.reply_channel = 'foo' - with mock.patch('channels.Group') as group_mock: - awx.network_ui.consumers.ws_disconnect(message) - group_mock.assert_called_once_with('topology-1') - group_mock.return_value.discard.assert_called_once_with('foo') - - -def test_ws_disconnect_no_topology(): - message = mock.MagicMock() - with mock.patch('channels.Group') as group_mock: - awx.network_ui.consumers.ws_disconnect(message) - group_mock.assert_not_called() - - -def test_ws_message(): - message = mock.MagicMock() - message.channel_session = dict(topology_id=1, client_id=1) - message.__getitem__.return_value = json.dumps([]) - print (message['text']) - with mock.patch('channels.Group') as group_mock: - awx.network_ui.consumers.ws_message(message) - group_mock.assert_called_once_with('topology-1') - group_mock.return_value.send.assert_called_once_with({'text': '[]'}) - - -def test_ws_connect_unauthenticated(): - message = mock.MagicMock() - message.user.is_authenticated.return_value = False - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch.object(logger, 'error') as log_mock: - awx.network_ui.consumers.ws_connect(message) - log_mock.assert_called_once_with('Request user is not authenticated to use websocket.') - - -def test_ws_connect_new_topology(): - mock_user = mock.Mock() - message = mock.MagicMock(user=mock_user) - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch('awx.network_ui.consumers.uuid') as client_mock,\ - mock.patch('awx.network_ui.consumers.Topology') as topology_mock,\ - mock.patch('channels.Group'),\ - mock.patch('awx.network_ui.consumers.send_snapshot') as send_snapshot_mock,\ - mock.patch.object(logger, 'warning'),\ - mock.patch.object(TopologyInventory, 'objects'),\ - mock.patch.object(TopologyInventory, 'save'),\ - mock.patch.object(Topology, 'save'),\ - mock.patch.object(Topology, 'objects'),\ - mock.patch.object(Device, 'objects'),\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects'),\ - mock.patch.object(Inventory, 'objects') as inventory_objects: - client_mock.uuid4 = mock.MagicMock(return_value="777") - topology_mock.return_value = Topology( - name="topology", scale=0.7, panX=0, panY=0, pk=999) - inventory_objects.get.return_value = mock.Mock(admin_role=[mock_user]) - awx.network_ui.consumers.ws_connect(message) - message.reply_channel.send.assert_has_calls([ - mock.call({'text': '["id", "777"]'}), - mock.call({'text': '["topology_id", 999]'}), - mock.call( - {'text': '["Topology", {"scale": 1.0, "name": "topology", "device_id_seq": 0, "panY": 0, "panX": 0, "topology_id": 999, "link_id_seq": 0}]'}), - ]) - send_snapshot_mock.assert_called_once_with(message.reply_channel, 999) - - -def test_ws_connect_existing_topology(): - mock_user = mock.Mock() - message = mock.MagicMock(user=mock_user) - logger = logging.getLogger('awx.network_ui.consumers') - with mock.patch('awx.network_ui.consumers.uuid') as client_mock,\ - mock.patch('awx.network_ui.consumers.send_snapshot') as send_snapshot_mock,\ - mock.patch('channels.Group'),\ - mock.patch.object(logger, 'warning'),\ - mock.patch.object(TopologyInventory, 'objects') as topology_inventory_objects_mock,\ - mock.patch.object(TopologyInventory, 'save'),\ - mock.patch.object(Topology, 'save'),\ - mock.patch.object(Topology, 'objects') as topology_objects_mock,\ - mock.patch.object(Device, 'objects'),\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects'),\ - mock.patch.object(Inventory, 'objects') as inventory_objects: - topology_inventory_objects_mock.filter.return_value.values_list.return_value = [ - 1] - client_mock.uuid4 = mock.MagicMock(return_value="888") - topology_objects_mock.get.return_value = Topology(pk=1001, - id=1, - name="topo", - panX=0, - panY=0, - scale=1.0, - link_id_seq=1, - device_id_seq=1) - inventory_objects.get.return_value = mock.Mock(admin_role=[mock_user]) - awx.network_ui.consumers.ws_connect(message) - message.reply_channel.send.assert_has_calls([ - mock.call({'text': '["id", "888"]'}), - mock.call({'text': '["topology_id", 1001]'}), - mock.call( - {'text': '["Topology", {"scale": 1.0, "name": "topo", "device_id_seq": 1, "panY": 0, "panX": 0, "topology_id": 1001, "link_id_seq": 1}]'}), - ]) - send_snapshot_mock.assert_called_once_with(message.reply_channel, 1001) diff --git a/awx/network_ui/tests/functional/test_models.py b/awx/network_ui/tests/functional/test_models.py deleted file mode 100644 index 8d439dca22..0000000000 --- a/awx/network_ui/tests/functional/test_models.py +++ /dev/null @@ -1,54 +0,0 @@ -import pytest - -import inspect - -from awx.network_ui.models import Device, Topology, Interface, Link - -from awx.main.models import Organization, Inventory -from awx.main.tasks import delete_inventory - -from django.db.models import Model - - -def test_device(): - assert str(Device(name="foo")) == "foo" - - -def test_topology(): - assert str(Topology(name="foo")) == "foo" - - -def test_interface(): - assert str(Interface(name="foo")) == "foo" - - -@pytest.mark.django_db -def test_deletion(): - org = Organization.objects.create(name='Default') - inv = Inventory.objects.create(name='inv', organization=org) - host1 = inv.hosts.create(name='foo') - host2 = inv.hosts.create(name='bar') - topology = Topology.objects.create( - name='inv', scale=0.7, panX=0.0, panY=0.0 - ) - inv.topologyinventory_set.create(topology=topology) - device1 = topology.device_set.create(name='foo', host=host1, x=0.0, y=0.0, cid=1) - interface1 = Interface.objects.create(device=device1, name='foo', cid=2) - device2 = topology.device_set.create(name='bar', host=host2, x=0.0, y=0.0, cid=3) - interface2 = Interface.objects.create(device=device2, name='bar', cid=4) - Link.objects.create( - from_device=device1, to_device=device2, - from_interface=interface1, to_interface=interface2, - cid=10 - ) - - network_ui_models = [] - from awx.network_ui import models as network_models - for name, model in vars(network_models).items(): - if not inspect.isclass(model) or not issubclass(model, Model): - continue - network_ui_models.append(model) - - delete_inventory.run(inv.pk, None) - for cls in network_ui_models: - assert cls.objects.count() == 0, cls diff --git a/awx/network_ui/tests/functional/test_network_events.py b/awx/network_ui/tests/functional/test_network_events.py deleted file mode 100644 index d4ce60c7ae..0000000000 --- a/awx/network_ui/tests/functional/test_network_events.py +++ /dev/null @@ -1,451 +0,0 @@ -import mock -import json -import logging - -from awx.network_ui.consumers import networking_events_dispatcher -from awx.network_ui.models import Topology, Device, Link, Interface - - -def message(message): - def wrapper(fn): - fn.tests_message = message - return fn - return wrapper - - -@message('DeviceMove') -def test_network_events_handle_message_DeviceMove(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceMove', dict( - msg_type='DeviceMove', - sender=1, - id=1, - x=100, - y=100, - previous_x=0, - previous_y=0 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock: - networking_events_dispatcher.handle(message) - device_objects_mock.filter.assert_called_once_with( - cid=1, topology_id=1) - device_objects_mock.filter.return_value.update.assert_called_once_with( - x=100, y=100) - log_mock.assert_not_called() - - -@message('DeviceCreate') -def test_network_events_handle_message_DeviceCreate(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceCreate', dict(msg_type='DeviceCreate', - sender=1, - id=1, - x=0, - y=0, - name="test_created", - type='host', - host_id=None)] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Topology.objects, 'filter') as topology_objects_mock,\ - mock.patch.object(Device.objects, 'get_or_create') as device_objects_mock: - device_mock = mock.MagicMock() - filter_mock = mock.MagicMock() - device_objects_mock.return_value = [device_mock, True] - topology_objects_mock.return_value = filter_mock - networking_events_dispatcher.handle(message) - device_objects_mock.assert_called_once_with( - cid=1, - defaults={'name': u'test_created', 'cid': 1, 'device_type': u'host', - 'x': 0, 'y': 0, 'host_id': None}, - topology_id=1) - device_mock.save.assert_called_once_with() - topology_objects_mock.assert_called_once_with( - device_id_seq__lt=1, pk=1) - filter_mock.update.assert_called_once_with(device_id_seq=1) - log_mock.assert_not_called() - - -@message('DeviceLabelEdit') -def test_network_events_handle_message_DeviceLabelEdit(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceLabelEdit', dict( - msg_type='DeviceLabelEdit', - sender=1, - id=1, - name='test_changed', - previous_name='test_created' - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device.objects, 'filter') as device_objects_filter_mock: - networking_events_dispatcher.handle(message) - device_objects_filter_mock.assert_called_once_with( - cid=1, topology_id=1) - log_mock.assert_not_called() - - -@message('DeviceSelected') -def test_network_events_handle_message_DeviceSelected(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceSelected', dict( - msg_type='DeviceSelected', - sender=1, - id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_not_called() - - -@message('DeviceUnSelected') -def test_network_events_handle_message_DeviceUnSelected(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceUnSelected', dict( - msg_type='DeviceUnSelected', - sender=1, - id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_not_called() - - -@message('DeviceDestroy') -def test_network_events_handle_message_DeviceDestory(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['DeviceDestroy', dict( - msg_type='DeviceDestroy', - sender=1, - id=1, - previous_x=0, - previous_y=0, - previous_name="", - previous_type="host", - previous_host_id="1")] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock: - networking_events_dispatcher.handle(message) - device_objects_mock.filter.assert_called_once_with( - cid=1, topology_id=1) - device_objects_mock.filter.return_value.delete.assert_called_once_with() - log_mock.assert_not_called() - - -@message('InterfaceCreate') -def test_network_events_handle_message_InterfaceCreate(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['InterfaceCreate', dict( - msg_type='InterfaceCreate', - sender=1, - device_id=1, - id=1, - name='eth0' - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Interface, 'objects') as interface_objects_mock: - device_objects_mock.get.return_value.pk = 99 - networking_events_dispatcher.handle(message) - device_objects_mock.get.assert_called_once_with(cid=1, topology_id=1) - device_objects_mock.filter.assert_called_once_with( - cid=1, interface_id_seq__lt=1, topology_id=1) - interface_objects_mock.get_or_create.assert_called_once_with( - cid=1, defaults={'name': u'eth0'}, device_id=99) - log_mock.assert_not_called() - - -@message('InterfaceLabelEdit') -def test_network_events_handle_message_InterfaceLabelEdit(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['InterfaceLabelEdit', dict( - msg_type='InterfaceLabelEdit', - sender=1, - id=1, - device_id=1, - name='new name', - previous_name='old name' - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Interface, 'objects') as interface_objects_mock: - networking_events_dispatcher.handle(message) - interface_objects_mock.filter.assert_called_once_with( - cid=1, device__cid=1, device__topology_id=1) - interface_objects_mock.filter.return_value.update.assert_called_once_with( - name=u'new name') - log_mock.assert_not_called() - - -@message('LinkLabelEdit') -def test_network_events_handle_message_LinkLabelEdit(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkLabelEdit', dict( - msg_type='LinkLabelEdit', - sender=1, - id=1, - name='new name', - previous_name='old name' - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Link, 'objects') as link_objects_mock: - networking_events_dispatcher.handle(message) - link_objects_mock.filter.assert_called_once_with( - cid=1, from_device__topology_id=1) - link_objects_mock.filter.return_value.update.assert_called_once_with( - name=u'new name') - log_mock.assert_not_called() - - -@message('LinkCreate') -def test_network_events_handle_message_LinkCreate(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkCreate', dict( - msg_type='LinkCreate', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Link, 'objects') as link_objects_mock,\ - mock.patch.object(Interface, 'objects') as interface_objects_mock,\ - mock.patch.object(Topology, 'objects') as topology_objects_mock: - values_list_mock = mock.MagicMock() - values_list_mock.values_list.return_value = [(1,1), (2,2)] - interface_objects_mock.get.return_value = mock.MagicMock() - interface_objects_mock.get.return_value.pk = 7 - device_objects_mock.filter.return_value = values_list_mock - topology_objects_mock.filter.return_value = mock.MagicMock() - networking_events_dispatcher.handle(message) - device_objects_mock.filter.assert_called_once_with( - cid__in=[1, 2], topology_id=1) - values_list_mock.values_list.assert_called_once_with('cid', 'pk') - link_objects_mock.get_or_create.assert_called_once_with( - cid=1, from_device_id=1, from_interface_id=7, name=u'', - to_device_id=2, to_interface_id=7) - topology_objects_mock.filter.assert_called_once_with( - link_id_seq__lt=1, pk=1) - topology_objects_mock.filter.return_value.update.assert_called_once_with( - link_id_seq=1) - log_mock.assert_not_called() - - -@message('LinkCreate') -def test_network_events_handle_message_LinkCreate_bad_device1(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkCreate', dict( - msg_type='LinkCreate', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects') as interface_objects_mock,\ - mock.patch.object(Topology, 'objects') as topology_objects_mock: - values_list_mock = mock.MagicMock() - values_list_mock.values_list.return_value = [(9,1), (2,2)] - interface_objects_mock.get.return_value = mock.MagicMock() - interface_objects_mock.get.return_value.pk = 7 - device_objects_mock.filter.return_value = values_list_mock - topology_objects_mock.filter.return_value = mock.MagicMock() - networking_events_dispatcher.handle(message) - device_objects_mock.filter.assert_called_once_with( - cid__in=[1, 2], topology_id=1) - values_list_mock.values_list.assert_called_once_with('cid', 'pk') - log_mock.assert_called_once_with('Device not found') - - -@message('LinkCreate') -def test_network_events_handle_message_LinkCreate_bad_device2(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkCreate', dict( - msg_type='LinkCreate', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Link, 'objects'),\ - mock.patch.object(Interface, 'objects') as interface_objects_mock,\ - mock.patch.object(Topology, 'objects') as topology_objects_mock: - values_list_mock = mock.MagicMock() - values_list_mock.values_list.return_value = [(1,1), (9,2)] - interface_objects_mock.get.return_value = mock.MagicMock() - interface_objects_mock.get.return_value.pk = 7 - device_objects_mock.filter.return_value = values_list_mock - topology_objects_mock.filter.return_value = mock.MagicMock() - networking_events_dispatcher.handle(message) - device_objects_mock.filter.assert_called_once_with( - cid__in=[1, 2], topology_id=1) - values_list_mock.values_list.assert_called_once_with('cid', 'pk') - log_mock.assert_called_once_with('Device not found') - - -@message('LinkDestroy') -def test_network_events_handle_message_LinkDestroy(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkDestroy', dict( - msg_type='LinkDestroy', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device.objects, 'filter') as device_filter_mock,\ - mock.patch.object(Link.objects, 'filter') as link_filter_mock,\ - mock.patch.object(Interface.objects, 'get') as interface_get_mock: - values_mock = mock.MagicMock() - interface_get_mock.return_value = mock.MagicMock() - interface_get_mock.return_value.pk = 7 - device_filter_mock.return_value = values_mock - values_mock.values_list.return_value = [(1,1), (2,2)] - networking_events_dispatcher.handle(message) - device_filter_mock.assert_called_once_with( - cid__in=[1, 2], topology_id=1) - values_mock.values_list.assert_called_once_with('cid', 'pk') - link_filter_mock.assert_called_once_with( - cid=1, from_device_id=1, from_interface_id=7, to_device_id=2, to_interface_id=7) - log_mock.assert_not_called() - - -@message('LinkDestroy') -def test_network_events_handle_message_LinkDestroy_bad_device_map1(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkDestroy', dict( - msg_type='LinkDestroy', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device.objects, 'filter') as device_filter_mock,\ - mock.patch.object(Link.objects, 'filter'),\ - mock.patch.object(Interface.objects, 'get') as interface_get_mock: - values_mock = mock.MagicMock() - interface_get_mock.return_value = mock.MagicMock() - interface_get_mock.return_value.pk = 7 - device_filter_mock.return_value = values_mock - values_mock.values_list.return_value = [(9,1), (2,2)] - networking_events_dispatcher.handle(message) - log_mock.assert_called_once_with('Device not found') - - -@message('LinkDestroy') -def test_network_events_handle_message_LinkDestroy_bad_device_map2(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkDestroy', dict( - msg_type='LinkDestroy', - id=1, - sender=1, - name="", - from_device_id=1, - to_device_id=2, - from_interface_id=1, - to_interface_id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock,\ - mock.patch.object(Device.objects, 'filter') as device_filter_mock,\ - mock.patch.object(Link.objects, 'filter'),\ - mock.patch.object(Interface.objects, 'get') as interface_get_mock: - values_mock = mock.MagicMock() - interface_get_mock.return_value = mock.MagicMock() - interface_get_mock.return_value.pk = 7 - device_filter_mock.return_value = values_mock - values_mock.values_list.return_value = [(1,1), (9,2)] - networking_events_dispatcher.handle(message) - log_mock.assert_called_once_with('Device not found') - - -@message('LinkSelected') -def test_network_events_handle_message_LinkSelected(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkSelected', dict( - msg_type='LinkSelected', - sender=1, - id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_not_called() - - -@message('LinkUnSelected') -def test_network_events_handle_message_LinkUnSelected(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['LinkUnSelected', dict( - msg_type='LinkUnSelected', - sender=1, - id=1 - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_not_called() - - -@message('MultipleMessage') -def test_network_events_handle_message_MultipleMessage_unsupported_message(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['MultipleMessage', dict( - msg_type='MultipleMessage', - sender=1, - messages=[dict(msg_type="Unsupported")] - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_called_once_with( - 'Unsupported message %s', u'Unsupported') - - -@message('MultipleMessage') -def test_network_events_handle_message_MultipleMessage(): - logger = logging.getLogger('awx.network_ui.consumers') - message_data = ['MultipleMessage', dict( - msg_type='MultipleMessage', - sender=1, - messages=[dict(msg_type="DeviceSelected")] - )] - message = {'topology': 1, 'client': 1, 'text': json.dumps(message_data)} - with mock.patch.object(logger, 'warning') as log_mock: - networking_events_dispatcher.handle(message) - log_mock.assert_not_called() diff --git a/awx/network_ui/tests/functional/test_routing.py b/awx/network_ui/tests/functional/test_routing.py deleted file mode 100644 index d1d7a741dd..0000000000 --- a/awx/network_ui/tests/functional/test_routing.py +++ /dev/null @@ -1,9 +0,0 @@ - -import awx.network_ui.routing - - -def test_routing(): - ''' - Tests that the number of routes in awx.network_ui.routing is 3. - ''' - assert len(awx.network_ui.routing.channel_routing) == 3 diff --git a/awx/network_ui/tests/functional/test_views.py b/awx/network_ui/tests/functional/test_views.py deleted file mode 100644 index 9b55ad72d4..0000000000 --- a/awx/network_ui/tests/functional/test_views.py +++ /dev/null @@ -1,65 +0,0 @@ - -import mock - -from awx.network_ui.views import topology_data, NetworkAnnotatedInterface, json_topology_data, yaml_topology_data -from awx.network_ui.models import Topology, Device, Link, Interface - - - -def test_topology_data(): - with mock.patch.object(Topology, 'objects'),\ - mock.patch.object(Device, 'objects') as device_objects_mock,\ - mock.patch.object(Link, 'objects') as link_objects_mock,\ - mock.patch.object(Interface, 'objects'),\ - mock.patch.object(NetworkAnnotatedInterface, 'filter'): - device_objects_mock.filter.return_value.order_by.return_value = [ - Device(pk=1), Device(pk=2)] - link_objects_mock.filter.return_value = [Link(from_device=Device(name='from', cid=1), - to_device=Device( - name='to', cid=2), - from_interface=Interface( - name="eth0", cid=1), - to_interface=Interface( - name="eth0", cid=1), - name="", - pk=1 - )] - data = topology_data(1) - assert len(data['devices']) == 2 - assert len(data['links']) == 1 - - -def test_json_topology_data(): - request = mock.MagicMock() - request.GET = dict(topology_id=1) - with mock.patch('awx.network_ui.views.topology_data') as topology_data_mock: - topology_data_mock.return_value = dict() - json_topology_data(request) - topology_data_mock.assert_called_once_with(1) - - -def test_yaml_topology_data(): - request = mock.MagicMock() - request.GET = dict(topology_id=1) - with mock.patch('awx.network_ui.views.topology_data') as topology_data_mock: - topology_data_mock.return_value = dict() - yaml_topology_data(request) - topology_data_mock.assert_called_once_with(1) - - -def test_json_topology_data_no_topology_id(): - request = mock.MagicMock() - request.GET = dict() - with mock.patch('awx.network_ui.views.topology_data') as topology_data_mock: - topology_data_mock.return_value = dict() - json_topology_data(request) - topology_data_mock.assert_not_called() - - -def test_yaml_topology_data_no_topology_id(): - request = mock.MagicMock() - request.GET = dict() - with mock.patch('awx.network_ui.views.topology_data') as topology_data_mock: - topology_data_mock.return_value = dict() - yaml_topology_data(request) - topology_data_mock.assert_not_called() diff --git a/awx/network_ui/urls.py b/awx/network_ui/urls.py deleted file mode 100644 index 2101eff59f..0000000000 --- a/awx/network_ui/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc -from django.conf.urls import url - -from awx.network_ui import views - -app_name = 'network_ui' -urlpatterns = [ - url(r'^topology.json/?$', views.json_topology_data, name='json_topology_data'), - url(r'^topology.yaml/?$', views.yaml_topology_data, name='yaml_topology_data'), -] diff --git a/awx/network_ui/utils.py b/awx/network_ui/utils.py deleted file mode 100644 index 9b2eea6c10..0000000000 --- a/awx/network_ui/utils.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc - - -def transform_dict(dict_map, d): - return {to_key: d[from_key] for from_key, to_key in dict_map.iteritems()} - diff --git a/awx/network_ui/views.py b/awx/network_ui/views.py deleted file mode 100644 index b9cd476bcc..0000000000 --- a/awx/network_ui/views.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc -from django import forms -from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse -from awx.network_ui.models import Topology, Device, Link, Interface -from django.db.models import Q -import yaml - -NetworkAnnotatedInterface = Interface.objects.values('name', - 'cid', - 'from_link__pk', - 'to_link__pk', - 'from_link__to_device__name', - 'to_link__from_device__name', - 'from_link__to_interface__name', - 'to_link__from_interface__name') - - -def topology_data(topology_id): - - data = dict(devices=[], - links=[]) - - topology = Topology.objects.get(pk=topology_id) - - data['name'] = topology.name - data['topology_id'] = topology_id - - links = list(Link.objects - .filter(Q(from_device__topology_id=topology_id) | - Q(to_device__topology_id=topology_id))) - - interfaces = Interface.objects.filter(device__topology_id=topology_id) - - for device in Device.objects.filter(topology_id=topology_id).order_by('name'): - interfaces = list(NetworkAnnotatedInterface.filter(device_id=device.pk).order_by('name')) - interfaces = [dict(name=x['name'], - network=x['from_link__pk'] or x['to_link__pk'], - remote_device_name=x['from_link__to_device__name'] or x['to_link__from_device__name'], - remote_interface_name=x['from_link__to_interface__name'] or x['to_link__from_interface__name'], - id=x['cid'], - ) for x in interfaces] - data['devices'].append(dict(name=device.name, - type=device.device_type, - x=device.x, - y=device.y, - id=device.cid, - interfaces=interfaces)) - - for link in links: - data['links'].append(dict(from_device=link.from_device.name, - to_device=link.to_device.name, - from_interface=link.from_interface.name, - to_interface=link.to_interface.name, - from_device_id=link.from_device.cid, - to_device_id=link.to_device.cid, - from_interface_id=link.from_interface.cid, - to_interface_id=link.to_interface.cid, - name=link.name, - network=link.pk)) - - return data - - -class TopologyForm(forms.Form): - topology_id = forms.IntegerField() - - -def json_topology_data(request): - form = TopologyForm(request.GET) - if form.is_valid(): - response = JsonResponse(topology_data(form.cleaned_data['topology_id']), - content_type='application/force-download') - response['Content-Disposition'] = 'attachment; filename="{}"'.format('topology.json') - return response - else: - return HttpResponseBadRequest(form.errors) - - -def yaml_topology_data(request): - form = TopologyForm(request.GET) - if form.is_valid(): - response = HttpResponse(yaml.safe_dump(topology_data(form.cleaned_data['topology_id']), - default_flow_style=False), - content_type='application/force-download') - response['Content-Disposition'] = 'attachment; filename="{}"'.format('topology.yaml') - return response - else: - return HttpResponseBadRequest(form.errors) - diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 818713431e..48440022d5 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -289,8 +289,7 @@ INSTALLED_APPS = ( 'awx.api', 'awx.ui', 'awx.sso', - 'solo', - 'awx.network_ui' + 'solo' ) INTERNAL_IPS = ('127.0.0.1',) diff --git a/awx/ui/build/webpack.base.js b/awx/ui/build/webpack.base.js index 633987dde0..e0e0eb383b 100644 --- a/awx/ui/build/webpack.base.js +++ b/awx/ui/build/webpack.base.js @@ -22,7 +22,6 @@ const SRC_PATH = path.join(CLIENT_PATH, 'src'); const STATIC_PATH = path.join(UI_PATH, 'static'); const TEST_PATH = path.join(UI_PATH, 'test'); const THEME_PATH = path.join(LIB_PATH, 'theme'); -const NETWORK_UI_PATH = path.join(SRC_PATH, 'network-ui'); const APP_ENTRY = path.join(SRC_PATH, 'app.js'); const VENDOR_ENTRY = path.join(SRC_PATH, 'vendor.js'); @@ -208,7 +207,6 @@ const base = { '~test': TEST_PATH, '~theme': THEME_PATH, '~ui': UI_PATH, - '~network-ui': NETWORK_UI_PATH, d3$: '~node_modules/d3/d3.min.js', 'codemirror.jsonlint$': '~node_modules/codemirror/addon/lint/json-lint.js', jquery: '~node_modules/jquery/dist/jquery.js', diff --git a/awx/ui/build/webpack.watch.js b/awx/ui/build/webpack.watch.js index 5bf1aad89c..143058077a 100644 --- a/awx/ui/build/webpack.watch.js +++ b/awx/ui/build/webpack.watch.js @@ -77,12 +77,6 @@ const watch = { target: TARGET, secure: false, ws: true - }, - { - context: '/network_ui', - target: TARGET, - secure: false, - ws: true }] } }; diff --git a/awx/ui/client/index.template.ejs b/awx/ui/client/index.template.ejs index 146d3290d7..b28c5b13ec 100644 --- a/awx/ui/client/index.template.ejs +++ b/awx/ui/client/index.template.ejs @@ -19,7 +19,6 @@
-