From 766bee37534a382cb82eaa7c1898c8c7c20a4349 Mon Sep 17 00:00:00 2001 From: Ben Thomasson Date: Thu, 15 Mar 2018 13:09:23 -0400 Subject: [PATCH] Refactors network_ui_test out of network_ui * Separates test messages from application messages * Removes test runner and groups, processes, and streams from network_ui * Adds network_ui_test * Fixes routing for network_ui_test * Removes coverage_report tool from network_ui * Fixes network_ui_test test workflow * Sets width and height of the page during tests --- awx/main/routing.py | 6 +- awx/network_ui/consumers.py | 224 +-------- .../migrations/0005_auto_20180306_1837.py | 147 ++++++ awx/network_ui/models.py | 147 ------ awx/network_ui/serializers.py | 40 +- awx/network_ui/urls.py | 5 - awx/network_ui/views.py | 105 +---- awx/network_ui_test/__init__.py | 1 + awx/network_ui_test/consumers.py | 205 +++++++++ .../migrations/0001_initial.py | 102 ++++ .../migrations/0002_auto_20180306_2243.py | 34 ++ .../migrations/0003_auto_20180306_2306.py | 26 ++ awx/network_ui_test/migrations/__init__.py | 0 awx/network_ui_test/models.py | 73 +++ awx/network_ui_test/routing.py | 12 + .../network_ui_test}/upload_test.html | 2 +- awx/network_ui_test/tools/Makefile | 13 + .../tools/coverage_report.py | 2 +- awx/network_ui_test/tools/requirements.txt | 2 + awx/network_ui_test/urls.py | 14 + awx/network_ui_test/views.py | 112 +++++ awx/settings/defaults.py | 1 + awx/ui/client/src/network-ui/messages.js | 4 +- awx/ui/client/src/network-ui/move.fsm.js | 1 - .../src/network-ui/network.ui.controller.js | 435 ++++-------------- awx/ui/client/src/network-ui/null.fsm.js | 36 -- awx/ui/client/src/network-ui/test.fsm.js | 52 ++- awx/ui/client/src/network-ui/time.fsm.js | 47 +- awx/ui/client/src/network-ui/view.fsm.js | 1 - 29 files changed, 920 insertions(+), 929 deletions(-) create mode 100644 awx/network_ui/migrations/0005_auto_20180306_1837.py create mode 100644 awx/network_ui_test/__init__.py create mode 100644 awx/network_ui_test/consumers.py create mode 100644 awx/network_ui_test/migrations/0001_initial.py create mode 100644 awx/network_ui_test/migrations/0002_auto_20180306_2243.py create mode 100644 awx/network_ui_test/migrations/0003_auto_20180306_2306.py create mode 100644 awx/network_ui_test/migrations/__init__.py create mode 100644 awx/network_ui_test/models.py create mode 100644 awx/network_ui_test/routing.py rename awx/{network_ui/templates/network_ui => network_ui_test/templates/network_ui_test}/upload_test.html (50%) create mode 100644 awx/network_ui_test/tools/Makefile rename awx/{network_ui => network_ui_test}/tools/coverage_report.py (97%) create mode 100644 awx/network_ui_test/tools/requirements.txt create mode 100644 awx/network_ui_test/urls.py create mode 100644 awx/network_ui_test/views.py delete mode 100644 awx/ui/client/src/network-ui/null.fsm.js diff --git a/awx/main/routing.py b/awx/main/routing.py index facd5302c7..abda29bd8c 100644 --- a/awx/main/routing.py +++ b/awx/main/routing.py @@ -1,4 +1,6 @@ from channels.routing import route +from awx.network_ui.routing import channel_routing as network_ui_routing +from awx.network_ui_test.routing import channel_routing as network_ui_test_routing channel_routing = [ @@ -7,6 +9,6 @@ channel_routing = [ route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'), ] -from awx.network_ui.routing import channel_routing as network_routing -channel_routing += network_routing +channel_routing += network_ui_routing +channel_routing += network_ui_test_routing diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py index 189f349fe2..bda2e872a3 100644 --- a/awx/network_ui/consumers.py +++ b/awx/network_ui/consumers.py @@ -2,20 +2,12 @@ # In consumers.py from channels import Group, Channel from channels.sessions import channel_session -from awx.network_ui.models import Topology, Device, Link, Client, TopologyHistory, MessageType, Interface -from awx.network_ui.models import Group as DeviceGroup -from awx.network_ui.models import GroupDevice as GroupDeviceMap -from awx.network_ui.models import Process, Stream -from awx.network_ui.models import Toolbox, ToolboxItem -from awx.network_ui.models import FSMTrace, EventTrace, Coverage, TopologySnapshot +from awx.network_ui.models import Topology, Device, Link, Client, Interface from awx.network_ui.models import TopologyInventory -from awx.network_ui.models import TestCase, TestResult, CodeUnderTest, Result import urlparse -from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from collections import defaultdict import logging -from django.utils.dateparse import parse_datetime from awx.network_ui.utils import transform_dict @@ -71,16 +63,6 @@ class _Persistence(object): return message_type = data[0] message_value = data[1] - try: - message_type_id = MessageType.objects.get(name=message_type).pk - except ObjectDoesNotExist: - logger.warning("Unsupported message %s: no message type", message_type) - return - TopologyHistory(topology_id=topology_id, - client_id=client_id, - message_type_id=message_type_id, - message_id=data[1].get('message_id', 0), - message_data=message['text']).save() handler = self.get_handler(message_type) if handler is not None: try: @@ -127,9 +109,6 @@ class _Persistence(object): def onDeviceInventoryUpdate(self, device, topology_id, client_id): Device.objects.filter(topology_id=topology_id, id=device['id']).update(host_id=device['host_id']) - def onGroupInventoryUpdate(self, group, topology_id, client_id): - DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).update(inventory_group_id=group['group_id']) - def onDeviceLabelEdit(self, device, topology_id, client_id): Device.objects.filter(topology_id=topology_id, id=device['id']).update(name=device['name']) @@ -182,34 +161,6 @@ class _Persistence(object): to_interface_id=Interface.objects.get(device_id=device_map[link['to_device_id']], id=link['to_interface_id']).pk).delete() - def onProcessCreate(self, process, topology_id, client_id): - Process.objects.get_or_create(device_id=Device.objects.get(id=process['device_id'], - topology_id=topology_id).pk, - id=process['id'], - defaults=dict(name=process['name'], process_type=process['type'])) - (Device.objects - .filter(id=process['device_id'], - topology_id=topology_id, - interface_id_seq__lt=process['id']) - .update(interface_id_seq=process['id'])) - - def onStreamCreate(self, stream, topology_id, client_id): - device_map = dict(Device.objects - .filter(topology_id=topology_id, id__in=[stream['from_id'], stream['to_id']]) - .values_list('id', 'pk')) - logger.info("onStreamCreate %s", stream) - Stream.objects.get_or_create(id=stream['id'], - label='', - from_device_id=device_map[stream['from_id']], - to_device_id=device_map[stream['to_id']]) - (Topology.objects - .filter(topology_id=topology_id, stream_id_seq__lt=stream['id']) - .update(stream_id_seq=stream['id'])) - - def onCopySite(self, site, topology_id, client_id): - site_toolbox, _ = Toolbox.objects.get_or_create(name="Site") - ToolboxItem(toolbox=site_toolbox, data=json.dumps(site['site'])).save() - def onDeviceSelected(self, message_value, topology_id, client_id): 'Ignore DeviceSelected messages' pass @@ -234,121 +185,6 @@ class _Persistence(object): else: logger.warning("Unsupported message %s", message['msg_type']) - def onCoverageRequest(self, coverage, topology_id, client_id): - pass - - def onTestResult(self, test_result, topology_id, client_id): - xyz, _, rest = test_result['code_under_test'].partition('-') - commits_since, _, commit_hash = rest.partition('-') - commit_hash = commit_hash.strip('g') - - x, y, z = [int(i) for i in xyz.split('.')] - - code_under_test, _ = CodeUnderTest.objects.get_or_create(version_x=x, - version_y=y, - version_z=z, - commits_since=int(commits_since), - commit_hash=commit_hash) - - tr = TestResult(id=test_result['id'], - result_id=Result.objects.get(name=test_result['result']).pk, - test_case_id=TestCase.objects.get(name=test_result['name']).pk, - code_under_test_id=code_under_test.pk, - client_id=client_id, - time=parse_datetime(test_result['date'])) - tr.save() - - - def onCoverage(self, coverage, topology_id, client_id): - Coverage(test_result_id=TestResult.objects.get(id=coverage['result_id'], client_id=client_id).pk, - coverage_data=json.dumps(coverage['coverage'])).save() - - def onStartRecording(self, recording, topology_id, client_id): - pass - - def onStopRecording(self, recording, topology_id, client_id): - pass - - def write_event(self, event, topology_id, client_id): - if event.get('save', True): - EventTrace(trace_session_id=event['trace_id'], - event_data=json.dumps(event), - message_id=event['message_id'], - client_id=client_id).save() - - onViewPort = write_event - onMouseEvent = write_event - onTouchEvent = write_event - onMouseWheelEvent = write_event - onKeyEvent = write_event - - def onGroupCreate(self, group, topology_id, client_id): - logger.info("GroupCreate %s %s %s", group['id'], group['name'], group['type']) - group = transform_dict(dict(x1='x1', - y1='y1', - x2='x2', - y2='y2', - name='name', - id='id', - type='group_type', - group_id='inventory_group_id'), group) - d, _ = DeviceGroup.objects.get_or_create(topology_id=topology_id, id=group['id'], defaults=group) - d.x1 = group['x1'] - d.y1 = group['y1'] - d.x2 = group['x2'] - d.y2 = group['y2'] - d.group_type = group['group_type'] - d.save() - (Topology.objects - .filter(topology_id=topology_id, group_id_seq__lt=group['id']) - .update(group_id_seq=group['id'])) - - def onGroupDestroy(self, group, topology_id, client_id): - DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).delete() - - def onGroupLabelEdit(self, group, topology_id, client_id): - DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).update(name=group['name']) - - def onGroupMove(self, group, topology_id, client_id): - DeviceGroup.objects.filter(topology_id=topology_id, id=group['id']).update(x1=group['x1'], - y1=group['y1'], - x2=group['x2'], - y2=group['y2']) - - def onGroupMembership(self, group_membership, topology_id, client_id): - members = set(group_membership['members']) - group = DeviceGroup.objects.get(topology_id=topology_id, id=group_membership['id']) - existing = set(GroupDeviceMap.objects.filter(group=group).values_list('device__id', flat=True)) - new = members - existing - removed = existing - members - - GroupDeviceMap.objects.filter(group__group_id=group.group_id, - device__id__in=list(removed)).delete() - - device_map = dict(Device.objects.filter(topology_id=topology_id, id__in=list(new)).values_list('id', 'device_id')) - new_entries = [] - for i in new: - new_entries.append(GroupDeviceMap(group=group, - device_id=device_map[i])) - if new_entries: - GroupDeviceMap.objects.bulk_create(new_entries) - - def onFSMTrace(self, message_value, diagram_id, client_id): - FSMTrace(trace_session_id=message_value['trace_id'], - fsm_name=message_value['fsm_name'], - from_state=message_value['from_state'], - to_state=message_value['to_state'], - order=message_value['order'], - client_id=client_id, - message_type=message_value['recv_message_type'] or "none").save() - - def onSnapshot(self, snapshot, topology_id, client_id): - TopologySnapshot(trace_session_id=snapshot['trace_id'], - snapshot_data=json.dumps(snapshot), - order=snapshot['order'], - client_id=client_id, - topology_id=topology_id).save() - persistence = _Persistence() @@ -385,45 +221,23 @@ def ws_connect(message): panY='panY', scale='scale', link_id_seq='link_id_seq', - device_id_seq='device_id_seq', - group_id_seq='group_id_seq'), topology.__dict__) + 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) - send_history(message.reply_channel, topology_id) - send_toolboxes(message.reply_channel) - send_tests(message.reply_channel) - - -def send_toolboxes(channel): - for toolbox_item in ToolboxItem.objects.filter(toolbox__name__in=['Process', 'Device', 'Rack', 'Site']).values('toolbox__name', 'data'): - item = dict(toolbox_name=toolbox_item['toolbox__name'], - data=toolbox_item['data']) - channel.send({"text": json.dumps(["ToolboxItem", item])}) - - -def send_tests(channel): - for name, test_case_data in TestCase.objects.all().values_list('name', 'test_case_data'): - channel.send({"text": json.dumps(["TestCase", [name, json.loads(test_case_data)]])}) def send_snapshot(channel, topology_id): interfaces = defaultdict(list) - processes = defaultdict(list) for i in (Interface.objects .filter(device__topology_id=topology_id) .values()): interfaces[i['device_id']].append(i) - for i in (Process.objects - .filter(device__topology_id=topology_id) - .values()): - processes[i['device_id']].append(i) devices = list(Device.objects .filter(topology_id=topology_id).values()) for device in devices: device['interfaces'] = interfaces[device['device_id']] - device['processes'] = processes[device['device_id']] links = [dict(id=x['id'], name=x['name'], @@ -440,44 +254,12 @@ def send_snapshot(channel, topology_id): 'to_device__id', 'from_interface__id', 'to_interface__id'))] - groups = list(DeviceGroup.objects - .filter(topology_id=topology_id).values()) - group_map = {g['id']: g for g in groups} - for group_id, device_id in GroupDeviceMap.objects.filter(group__topology_id=topology_id).values_list('group__id', 'device__id'): - if 'members' not in group_map[group_id]: - group_map[group_id]['members'] = [device_id] - else: - group_map[group_id]['members'].append(device_id) - - streams = [dict(id=x['id'], - label=x['label'], - from_id=x['from_device__id'], - to_id=x['to_device__id']) - for x in list(Stream.objects - .filter(Q(from_device__topology_id=topology_id) | - Q(to_device__topology_id=topology_id)).values('id', - 'label', - 'from_device__id', - 'to_device__id'))] - snapshot = dict(sender=0, devices=devices, - links=links, - groups=groups, - streams=streams) + links=links) channel.send({"text": json.dumps(["Snapshot", snapshot])}) -def send_history(channel, topology_id): - history = list(TopologyHistory.objects - .filter(topology_id=topology_id) - .exclude(message_type__name__in=HISTORY_MESSAGE_IGNORE_TYPES) - .exclude(undone=True) - .order_by('pk') - .values_list('message_data', flat=True)[:1000]) - channel.send({"text": json.dumps(["History", history])}) - - @channel_session def ws_message(message): # Send to all clients editing the topology diff --git a/awx/network_ui/migrations/0005_auto_20180306_1837.py b/awx/network_ui/migrations/0005_auto_20180306_1837.py new file mode 100644 index 0000000000..d2dd9eae3b --- /dev/null +++ b/awx/network_ui/migrations/0005_auto_20180306_1837.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-03-06 18:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0004_auto_20180304_2122'), + ] + + operations = [ + migrations.RemoveField( + model_name='coverage', + name='test_result', + ), + migrations.RemoveField( + model_name='eventtrace', + name='client', + ), + migrations.RemoveField( + model_name='fsmtrace', + name='client', + ), + migrations.RemoveField( + model_name='group', + name='topology', + ), + migrations.RemoveField( + model_name='groupdevice', + name='device', + ), + migrations.RemoveField( + model_name='groupdevice', + name='group', + ), + migrations.RemoveField( + model_name='process', + name='device', + ), + migrations.RemoveField( + model_name='stream', + name='from_device', + ), + migrations.RemoveField( + model_name='stream', + name='to_device', + ), + migrations.RemoveField( + model_name='testresult', + name='client', + ), + migrations.RemoveField( + model_name='testresult', + name='code_under_test', + ), + migrations.RemoveField( + model_name='testresult', + name='result', + ), + migrations.RemoveField( + model_name='testresult', + name='test_case', + ), + migrations.RemoveField( + model_name='toolboxitem', + name='toolbox', + ), + migrations.RemoveField( + model_name='topologyhistory', + name='client', + ), + migrations.RemoveField( + model_name='topologyhistory', + name='message_type', + ), + migrations.RemoveField( + model_name='topologyhistory', + name='topology', + ), + migrations.RemoveField( + model_name='topologysnapshot', + name='client', + ), + migrations.RemoveField( + model_name='device', + name='process_id_seq', + ), + migrations.RemoveField( + model_name='topology', + name='group_id_seq', + ), + migrations.RemoveField( + model_name='topology', + name='stream_id_seq', + ), + migrations.DeleteModel( + name='CodeUnderTest', + ), + migrations.DeleteModel( + name='Coverage', + ), + migrations.DeleteModel( + name='EventTrace', + ), + migrations.DeleteModel( + name='FSMTrace', + ), + migrations.DeleteModel( + name='Group', + ), + migrations.DeleteModel( + name='GroupDevice', + ), + migrations.DeleteModel( + name='MessageType', + ), + migrations.DeleteModel( + name='Process', + ), + migrations.DeleteModel( + name='Result', + ), + migrations.DeleteModel( + name='Stream', + ), + migrations.DeleteModel( + name='TestCase', + ), + migrations.DeleteModel( + name='TestResult', + ), + migrations.DeleteModel( + name='Toolbox', + ), + migrations.DeleteModel( + name='ToolboxItem', + ), + migrations.DeleteModel( + name='TopologyHistory', + ), + migrations.DeleteModel( + name='TopologySnapshot', + ), + ] diff --git a/awx/network_ui/models.py b/awx/network_ui/models.py index 6ec53866fd..46ba702dc5 100644 --- a/awx/network_ui/models.py +++ b/awx/network_ui/models.py @@ -11,7 +11,6 @@ class Device(models.Model): id = models.IntegerField() device_type = models.CharField(max_length=200, blank=True) interface_id_seq = models.IntegerField(default=0,) - process_id_seq = models.IntegerField(default=0,) host_id = models.IntegerField(default=0,) def __unicode__(self): @@ -38,8 +37,6 @@ class Topology(models.Model): panY = models.FloatField() device_id_seq = models.IntegerField(default=0,) link_id_seq = models.IntegerField(default=0,) - group_id_seq = models.IntegerField(default=0,) - stream_id_seq = models.IntegerField(default=0,) def __unicode__(self): return self.name @@ -50,26 +47,6 @@ class Client(models.Model): client_id = models.AutoField(primary_key=True,) -class TopologyHistory(models.Model): - - topology_history_id = models.AutoField(primary_key=True,) - topology = models.ForeignKey('Topology',) - client = models.ForeignKey('Client',) - message_type = models.ForeignKey('MessageType',) - message_id = models.IntegerField() - message_data = models.TextField() - undone = models.BooleanField(default=False,) - - -class MessageType(models.Model): - - message_type_id = models.AutoField(primary_key=True,) - name = models.CharField(max_length=200, blank=True) - - def __unicode__(self): - return self.name - - class Interface(models.Model): interface_id = models.AutoField(primary_key=True,) @@ -81,132 +58,8 @@ class Interface(models.Model): return self.name -class Group(models.Model): - - group_id = models.AutoField(primary_key=True,) - id = models.IntegerField() - name = models.CharField(max_length=200, blank=True) - x1 = models.IntegerField() - y1 = models.IntegerField() - x2 = models.IntegerField() - y2 = models.IntegerField() - topology = models.ForeignKey('Topology',) - group_type = models.CharField(max_length=200, blank=True) - inventory_group_id = models.IntegerField(default=0,) - - -class GroupDevice(models.Model): - - group_device_id = models.AutoField(primary_key=True,) - group = models.ForeignKey('Group',) - device = models.ForeignKey('Device',) - - -class Stream(models.Model): - - stream_id = models.AutoField('Stream', primary_key=True,) - from_device = models.ForeignKey('Device', related_name='from_stream',) - to_device = models.ForeignKey('Device', related_name='to_stream',) - label = models.CharField(max_length=200, blank=True) - id = models.IntegerField(default=0,) - - -class Process(models.Model): - - process_id = models.AutoField(primary_key=True,) - device = models.ForeignKey('Device',) - name = models.CharField(max_length=200, blank=True) - process_type = models.CharField(max_length=200, blank=True) - id = models.IntegerField(default=0,) - - -class Toolbox(models.Model): - - toolbox_id = models.AutoField(primary_key=True,) - name = models.CharField(max_length=200, blank=True) - - -class ToolboxItem(models.Model): - - toolbox_item_id = models.AutoField(primary_key=True,) - toolbox = models.ForeignKey('Toolbox',) - data = models.TextField() - - -class FSMTrace(models.Model): - - fsm_trace_id = models.AutoField(primary_key=True,) - fsm_name = models.CharField(max_length=200, blank=True) - from_state = models.CharField(max_length=200, blank=True) - to_state = models.CharField(max_length=200, blank=True) - message_type = models.CharField(max_length=200, blank=True) - client = models.ForeignKey('Client',) - trace_session_id = models.IntegerField(default=0,) - order = models.IntegerField(default=0,) - - class TopologyInventory(models.Model): topology_inventory_id = models.AutoField(primary_key=True,) topology = models.ForeignKey('Topology',) inventory_id = models.IntegerField() - - -class EventTrace(models.Model): - - event_trace_id = models.AutoField(primary_key=True,) - client = models.ForeignKey('Client',) - trace_session_id = models.IntegerField(default=0,) - event_data = models.TextField() - message_id = models.IntegerField() - - -class Coverage(models.Model): - - coverage_id = models.AutoField(primary_key=True,) - coverage_data = models.TextField() - test_result = models.ForeignKey('TestResult',) - - -class TopologySnapshot(models.Model): - - topology_snapshot_id = models.AutoField(primary_key=True,) - client = models.ForeignKey('Client',) - topology_id = models.IntegerField() - trace_session_id = models.IntegerField() - snapshot_data = models.TextField('TopologySnapshot',) - order = models.IntegerField() - - -class TestCase(models.Model): - - test_case_id = models.AutoField(primary_key=True,) - name = models.CharField('TestCase', max_length=200, blank=True) - test_case_data = models.TextField() - - -class Result(models.Model): - - result_id = models.AutoField(primary_key=True,) - name = models.CharField(max_length=20, blank=True) - - -class CodeUnderTest(models.Model): - - code_under_test_id = models.AutoField('CodeUnderTest', primary_key=True,) - version_x = models.IntegerField() - version_y = models.IntegerField() - version_z = models.IntegerField() - commits_since = models.IntegerField() - commit_hash = models.CharField(max_length=40, blank=True) - - -class TestResult(models.Model): - - test_result_id = models.AutoField(primary_key=True,) - test_case = models.ForeignKey('TestCase',) - result = models.ForeignKey('Result',) - code_under_test = models.ForeignKey('CodeUnderTest',) - time = models.DateTimeField() - id = models.IntegerField(default=0,) - client = models.ForeignKey('Client',) diff --git a/awx/network_ui/serializers.py b/awx/network_ui/serializers.py index 5ee72c7ec5..80884d2784 100644 --- a/awx/network_ui/serializers.py +++ b/awx/network_ui/serializers.py @@ -1,10 +1,9 @@ # Copyright (c) 2017 Red Hat, Inc -from awx.network_ui.models import Topology, Device, Link, Interface, Group, GroupDevice, Process, Stream +from awx.network_ui.models import Topology, Device, Link, Interface from django.db.models import Q import yaml import json -from collections import defaultdict NetworkAnnotatedInterface = Interface.objects.values('name', 'id', @@ -19,36 +18,18 @@ NetworkAnnotatedInterface = Interface.objects.values('name', def topology_data(topology_id): data = dict(devices=[], - links=[], - groups=[], - streams=[]) + links=[]) topology = Topology.objects.get(pk=topology_id) data['name'] = topology.name data['topology_id'] = topology_id - groups = list(Group.objects.filter(topology_id=topology_id).values()) - group_devices = GroupDevice.objects.filter(group__topology_id=topology_id).values('group_id', 'device_id', 'device__name', 'group__name') - group_device_map = defaultdict(list) - - for group_device in group_devices: - group_device_map[group_device['group_id']].append(group_device) - - device_group_map = defaultdict(list) - for group_device in group_devices: - device_group_map[group_device['device_id']].append(group_device) - - for group in groups: - group['members'] = [x['device__name'] for x in group_device_map[group['group_id']]] - data['groups'] = groups - 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) - processes = Process.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')) @@ -58,15 +39,12 @@ def topology_data(topology_id): remote_interface_name=x['from_link__to_interface__name'] or x['to_link__from_interface__name'], id=x['id'], ) for x in interfaces] - processes = list(Process.objects.filter(device_id=device.pk).values()) data['devices'].append(dict(name=device.name, type=device.device_type, x=device.x, y=device.y, id=device.id, - interfaces=interfaces, - processes=processes, - groups=[x['group__name'] for x in device_group_map[device.device_id]])) + interfaces=interfaces)) for link in links: data['links'].append(dict(from_device=link.from_device.name, @@ -80,18 +58,6 @@ def topology_data(topology_id): name=link.name, network=link.pk)) - streams = list(Stream.objects - .filter(Q(from_device__topology_id=topology_id) | - Q(to_device__topology_id=topology_id))) - - for stream in streams: - data['streams'].append(dict(from_id=stream.from_device.id, - to_id=stream.to_device.id, - from_device=stream.from_device.name, - to_device=stream.to_device.name, - label=stream.label, - id=stream.id)) - return data diff --git a/awx/network_ui/urls.py b/awx/network_ui/urls.py index c679105371..a5f6ab9dbc 100644 --- a/awx/network_ui/urls.py +++ b/awx/network_ui/urls.py @@ -5,11 +5,6 @@ from awx.network_ui import views app_name = 'network_ui' urlpatterns = [ - url(r'^tests$', views.tests, name='tests'), - url(r'^upload_test$', views.upload_test, name='upload_test'), - url(r'^download_coverage/(?P[0-9]+)$', views.download_coverage, name='download_coverage'), - url(r'^download_trace$', views.download_trace, name='download_trace'), - url(r'^download_recording$', views.download_recording, name='download_recording'), url(r'^topology.json$', views.json_topology_data, name='json_topology_data'), url(r'^topology.yaml$', views.yaml_topology_data, name='json_topology_data'), ] diff --git a/awx/network_ui/views.py b/awx/network_ui/views.py index 428296fc7e..27ed47043b 100644 --- a/awx/network_ui/views.py +++ b/awx/network_ui/views.py @@ -1,16 +1,12 @@ # Copyright (c) 2017 Red Hat, Inc from django.shortcuts import render from django import forms -from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect -from django.core.exceptions import ObjectDoesNotExist +from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse +from awx.network_ui.models import Topology import yaml -import json - # Create your views here. -from .models import Topology, FSMTrace, EventTrace, TopologySnapshot -from .models import TestCase, TestResult, Coverage from .serializers import topology_data @@ -39,100 +35,3 @@ def yaml_topology_data(request): else: return HttpResponseBadRequest(form.errors) - -class FSMTraceForm(forms.Form): - topology_id = forms.IntegerField() - trace_id = forms.IntegerField() - client_id = forms.IntegerField() - - -def download_trace(request): - form = FSMTraceForm(request.GET) - if form.is_valid(): - topology_id = form.cleaned_data['topology_id'] - trace_id = form.cleaned_data['trace_id'] - client_id = form.cleaned_data['client_id'] - data = list(FSMTrace.objects.filter(trace_session_id=trace_id, - client_id=client_id).order_by('order').values()) - response = HttpResponse(yaml.safe_dump(data, default_flow_style=False), - content_type="application/force-download") - response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id) - return response - else: - return HttpResponse(form.errors) - - -class RecordingForm(forms.Form): - topology_id = forms.IntegerField() - trace_id = forms.IntegerField() - client_id = forms.IntegerField() - - -def download_recording(request): - form = RecordingForm(request.GET) - if form.is_valid(): - topology_id = form.cleaned_data['topology_id'] - trace_id = form.cleaned_data['trace_id'] - client_id = form.cleaned_data['client_id'] - data = dict() - data['event_trace'] = [json.loads(x) for x in EventTrace - .objects.filter(trace_session_id=trace_id, client_id=client_id) - .order_by('message_id') - .values_list('event_data', flat=True)] - data['fsm_trace'] = list(FSMTrace - .objects - .filter(trace_session_id=trace_id, client_id=client_id) - .order_by('order') - .values()) - data['snapshots'] = [json.loads(x) for x in TopologySnapshot - .objects.filter(trace_session_id=trace_id, client_id=client_id) - .order_by('order') - .values_list('snapshot_data', flat=True)] - response = HttpResponse(json.dumps(data, sort_keys=True, indent=4), - content_type="application/force-download") - response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id) - return response - else: - return HttpResponse(form.errors) - - -def tests(request): - tests = list(TestCase.objects.all().values('test_case_id', 'name')) - for x in tests: - x['coverage'] = "/network_ui/download_coverage/{0}".format(x['test_case_id']) - return JsonResponse(dict(tests=tests)) - - -def create_test(name, data): - try: - test_case = TestCase.objects.get(name=name) - test_case.test_case_data=json.dumps(data) - test_case.save() - except ObjectDoesNotExist: - TestCase(name=name, test_case_data=json.dumps(data)).save() - - -class UploadTestForm(forms.Form): - name = forms.CharField() - file = forms.FileField() - - -def upload_test(request): - if request.method == 'POST': - form = UploadTestForm(request.POST, request.FILES) - if form.is_valid(): - name = form.cleaned_data['name'] - data = json.loads(request.FILES['file'].read()) - create_test(name, data) - return HttpResponseRedirect('/network_ui/tests') - else: - form = UploadTestForm() - return render(request, 'network_ui/upload_test.html', {'form': form}) - - -def download_coverage(request, pk): - latest_tr = TestResult.objects.filter(test_case_id=pk).order_by('-time')[0] - coverage = Coverage.objects.get(test_result_id=latest_tr.pk) - response = HttpResponse(coverage.coverage_data, - content_type="application/json") - return response diff --git a/awx/network_ui_test/__init__.py b/awx/network_ui_test/__init__.py new file mode 100644 index 0000000000..d05f93956d --- /dev/null +++ b/awx/network_ui_test/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2017 Red Hat, Inc diff --git a/awx/network_ui_test/consumers.py b/awx/network_ui_test/consumers.py new file mode 100644 index 0000000000..c5b01cb80f --- /dev/null +++ b/awx/network_ui_test/consumers.py @@ -0,0 +1,205 @@ +# Copyright (c) 2018 Red Hat, Inc +# In consumers.py +from channels import Group, Channel +from channels.sessions import channel_session +from awx.network_ui.models import Topology, Client +from awx.network_ui.models import TopologyInventory +from awx.network_ui_test.models import FSMTrace, EventTrace, Coverage, TopologySnapshot +from awx.network_ui_test.models import TestCase, TestResult, CodeUnderTest, Result +import urlparse +import logging +from django.utils.dateparse import parse_datetime + + +from pprint import pformat + +import json +# Connected to websocket.connect + +HISTORY_MESSAGE_IGNORE_TYPES = ['DeviceSelected', + 'DeviceUnSelected', + 'LinkSelected', + 'LinkUnSelected', + 'MouseEvent', + 'MouseWheelEvent', + 'KeyEvent'] + + +logger = logging.getLogger("awx.network_ui_test.consumers") + + +class NetworkUIException(Exception): + + pass + + +def parse_inventory_id(data): + inventory_id = data.get('inventory_id', ['null']) + try: + inventory_id = int(inventory_id[0]) + except ValueError: + inventory_id = None + if not inventory_id: + inventory_id = None + return inventory_id + +# TestPersistence + + +class _TestPersistence(object): + + def handle(self, message): + topology_id = message.get('topology') + assert topology_id is not None, "No topology_id" + client_id = message.get('client') + assert client_id is not None, "No client_id" + data = json.loads(message['text']) + if isinstance(data[1], list): + logger.error("Message has no sender") + return + if isinstance(data[1], dict) and client_id != data[1].get('sender'): + logger.error("client_id mismatch expected: %s actual %s", client_id, data[1].get('sender')) + logger.error(pformat(data)) + return + message_type = data[0] + message_value = data[1] + handler = self.get_handler(message_type) + if handler is not None: + try: + handler(message_value, topology_id, client_id) + except NetworkUIException, e: + Group("client-%s" % client_id).send({"text": json.dumps(["Error", str(e)])}) + raise + except Exception, e: + Group("client-%s" % client_id).send({"text": json.dumps(["Error", "Server Error"])}) + raise + except BaseException, e: + Group("client-%s" % client_id).send({"text": json.dumps(["Error", "Server Error"])}) + raise + 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 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']) + + def onCoverageRequest(self, coverage, topology_id, client_id): + pass + + def onTestResult(self, test_result, topology_id, client_id): + xyz, _, rest = test_result['code_under_test'].partition('-') + commits_since, _, commit_hash = rest.partition('-') + commit_hash = commit_hash.strip('g') + + x, y, z = [int(i) for i in xyz.split('.')] + + code_under_test, _ = CodeUnderTest.objects.get_or_create(version_x=x, + version_y=y, + version_z=z, + commits_since=int(commits_since), + commit_hash=commit_hash) + + logger.error("TR: %s", test_result) + tr = TestResult(id=test_result['id'], + result_id=Result.objects.get(name=test_result['result']).pk, + test_case_id=TestCase.objects.get(name=test_result['name']).pk, + code_under_test_id=code_under_test.pk, + client_id=client_id, + time=parse_datetime(test_result['date'])) + tr.save() + + + def onCoverage(self, coverage, topology_id, client_id): + Coverage(test_result_id=TestResult.objects.get(id=coverage['result_id'], client_id=client_id).pk, + coverage_data=json.dumps(coverage['coverage'])).save() + + def onStartRecording(self, recording, topology_id, client_id): + pass + + def onStopRecording(self, recording, topology_id, client_id): + pass + + def write_event(self, event, topology_id, client_id): + if event.get('save', True): + EventTrace(trace_session_id=event['trace_id'], + event_data=json.dumps(event), + message_id=event['message_id'], + client_id=client_id).save() + + onViewPort = write_event + onMouseEvent = write_event + onTouchEvent = write_event + onMouseWheelEvent = write_event + onKeyEvent = write_event + + def onFSMTrace(self, message_value, diagram_id, client_id): + FSMTrace(trace_session_id=message_value['trace_id'], + fsm_name=message_value['fsm_name'], + from_state=message_value['from_state'], + to_state=message_value['to_state'], + order=message_value['order'], + client_id=client_id, + message_type=message_value['recv_message_type'] or "none").save() + + def onSnapshot(self, snapshot, topology_id, client_id): + TopologySnapshot(trace_session_id=snapshot['trace_id'], + snapshot_data=json.dumps(snapshot), + order=snapshot['order'], + client_id=client_id, + topology_id=topology_id).save() + + +test_persistence = _TestPersistence() + + +# UI Channel Events + +@channel_session +def ws_connect(message): + # Accept connection + data = urlparse.parse_qs(message.content['query_string']) + inventory_id = parse_inventory_id(data) + topology_ids = list(TopologyInventory.objects.filter(inventory_id=inventory_id).values_list('topology_id', flat=True)) + topology_id = 0 + if len(topology_ids) > 0: + topology_id = topology_ids[0] + if topology_id: + topology = Topology.objects.get(topology_id=topology_id) + else: + topology = Topology(name="topology", scale=1.0, panX=0, panY=0) + topology.save() + TopologyInventory(inventory_id=inventory_id, topology_id=topology.topology_id).save() + topology_id = topology.topology_id + message.channel_session['topology_id'] = topology_id + client = Client() + client.save() + message.channel_session['client_id'] = client.pk + Group("client-%s" % client.pk).add(message.reply_channel) + message.reply_channel.send({"text": json.dumps(["id", client.pk])}) + send_tests(message.reply_channel) + + +def send_tests(channel): + for name, test_case_data in TestCase.objects.all().values_list('name', 'test_case_data'): + channel.send({"text": json.dumps(["TestCase", [name, json.loads(test_case_data)]])}) + + +@channel_session +def ws_message(message): + Channel('test_persistence').send({"text": message['text'], + "topology": message.channel_session['topology_id'], + "client": message.channel_session['client_id']}) + + +@channel_session +def ws_disconnect(message): + pass + + diff --git a/awx/network_ui_test/migrations/0001_initial.py b/awx/network_ui_test/migrations/0001_initial.py new file mode 100644 index 0000000000..f47599c5bc --- /dev/null +++ b/awx/network_ui_test/migrations/0001_initial.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-03-06 18:40 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('network_ui', '0005_auto_20180306_1837'), + ] + + operations = [ + migrations.CreateModel( + name='CodeUnderTest', + fields=[ + ('code_under_test_id', models.AutoField(primary_key=True, serialize=False, verbose_name=b'CodeUnderTest')), + ('version_x', models.IntegerField()), + ('version_y', models.IntegerField()), + ('version_z', models.IntegerField()), + ('commits_since', models.IntegerField()), + ('commit_hash', models.CharField(blank=True, max_length=40)), + ], + ), + migrations.CreateModel( + name='Coverage', + fields=[ + ('coverage_id', models.AutoField(primary_key=True, serialize=False)), + ('coverage_data', models.TextField()), + ], + ), + migrations.CreateModel( + name='EventTrace', + fields=[ + ('event_trace_id', models.AutoField(primary_key=True, serialize=False)), + ('trace_session_id', models.IntegerField(default=0)), + ('event_data', models.TextField()), + ('message_id', models.IntegerField()), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')), + ], + ), + migrations.CreateModel( + name='FSMTrace', + fields=[ + ('fsm_trace_id', models.AutoField(primary_key=True, serialize=False)), + ('fsm_name', models.CharField(blank=True, max_length=200)), + ('from_state', models.CharField(blank=True, max_length=200)), + ('to_state', models.CharField(blank=True, max_length=200)), + ('message_type', models.CharField(blank=True, max_length=200)), + ('trace_session_id', models.IntegerField(default=0)), + ('order', models.IntegerField(default=0)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')), + ], + ), + migrations.CreateModel( + name='Result', + fields=[ + ('result_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=20)), + ], + ), + migrations.CreateModel( + name='TestCase', + fields=[ + ('test_case_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=200, verbose_name=b'TestCase')), + ('test_case_data', models.TextField()), + ], + ), + migrations.CreateModel( + name='TestResult', + fields=[ + ('test_result_id', models.AutoField(primary_key=True, serialize=False)), + ('time', models.DateTimeField()), + ('id', models.IntegerField(default=0)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')), + ('code_under_test', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.CodeUnderTest')), + ('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.Result')), + ('test_case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.TestCase')), + ], + ), + migrations.CreateModel( + name='TopologySnapshot', + fields=[ + ('topology_snapshot_id', models.AutoField(primary_key=True, serialize=False)), + ('topology_id', models.IntegerField()), + ('trace_session_id', models.IntegerField()), + ('snapshot_data', models.TextField(verbose_name=b'TopologySnapshot')), + ('order', models.IntegerField()), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')), + ], + ), + migrations.AddField( + model_name='coverage', + name='test_result', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.TestResult'), + ), + ] diff --git a/awx/network_ui_test/migrations/0002_auto_20180306_2243.py b/awx/network_ui_test/migrations/0002_auto_20180306_2243.py new file mode 100644 index 0000000000..217ed3d8e4 --- /dev/null +++ b/awx/network_ui_test/migrations/0002_auto_20180306_2243.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-03-06 22:43 +from __future__ import unicode_literals + +from django.db import migrations + + +results = ['passed', + 'failed', + 'errored', + 'skipped', + 'aborted', + 'not run', + 'blocked'] + + +def populate_result_types(apps, schema_editor): + + Result = apps.get_model('network_ui_test', 'Result') + for result in results: + Result.objects.get_or_create(name=result) + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui_test', '0001_initial'), + ] + + operations = [ + migrations.RunPython( + code=populate_result_types, + ), + ] diff --git a/awx/network_ui_test/migrations/0003_auto_20180306_2306.py b/awx/network_ui_test/migrations/0003_auto_20180306_2306.py new file mode 100644 index 0000000000..5cf7545908 --- /dev/null +++ b/awx/network_ui_test/migrations/0003_auto_20180306_2306.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-03-06 23:06 +from __future__ import unicode_literals + +from django.db import migrations + +import json + + +def add_load_test_case(apps, schema_editor): + + TestCase = apps.get_model('network_ui_test', 'TestCase') + TestCase.objects.get_or_create(name="Load", test_case_data=json.dumps(dict(runnable=False))) + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui_test', '0002_auto_20180306_2243'), + ] + + operations = [ + migrations.RunPython( + code=add_load_test_case, + ), + ] diff --git a/awx/network_ui_test/migrations/__init__.py b/awx/network_ui_test/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/network_ui_test/models.py b/awx/network_ui_test/models.py new file mode 100644 index 0000000000..f8f8095c21 --- /dev/null +++ b/awx/network_ui_test/models.py @@ -0,0 +1,73 @@ +from django.db import models + + +class FSMTrace(models.Model): + + fsm_trace_id = models.AutoField(primary_key=True,) + fsm_name = models.CharField(max_length=200, blank=True) + from_state = models.CharField(max_length=200, blank=True) + to_state = models.CharField(max_length=200, blank=True) + message_type = models.CharField(max_length=200, blank=True) + client = models.ForeignKey('network_ui.Client',) + trace_session_id = models.IntegerField(default=0,) + order = models.IntegerField(default=0,) + + +class EventTrace(models.Model): + + event_trace_id = models.AutoField(primary_key=True,) + client = models.ForeignKey('network_ui.Client',) + trace_session_id = models.IntegerField(default=0,) + event_data = models.TextField() + message_id = models.IntegerField() + + +class Coverage(models.Model): + + coverage_id = models.AutoField(primary_key=True,) + coverage_data = models.TextField() + test_result = models.ForeignKey('TestResult',) + + +class TopologySnapshot(models.Model): + + topology_snapshot_id = models.AutoField(primary_key=True,) + client = models.ForeignKey('network_ui.Client',) + topology_id = models.IntegerField() + trace_session_id = models.IntegerField() + snapshot_data = models.TextField('TopologySnapshot',) + order = models.IntegerField() + + +class TestCase(models.Model): + + test_case_id = models.AutoField(primary_key=True,) + name = models.CharField('TestCase', max_length=200, blank=True) + test_case_data = models.TextField() + + +class Result(models.Model): + + result_id = models.AutoField(primary_key=True,) + name = models.CharField(max_length=20, blank=True) + + +class CodeUnderTest(models.Model): + + code_under_test_id = models.AutoField('CodeUnderTest', primary_key=True,) + version_x = models.IntegerField() + version_y = models.IntegerField() + version_z = models.IntegerField() + commits_since = models.IntegerField() + commit_hash = models.CharField(max_length=40, blank=True) + + +class TestResult(models.Model): + + test_result_id = models.AutoField(primary_key=True,) + test_case = models.ForeignKey('TestCase',) + result = models.ForeignKey('Result',) + code_under_test = models.ForeignKey('CodeUnderTest',) + time = models.DateTimeField() + id = models.IntegerField(default=0,) + client = models.ForeignKey('network_ui.Client',) diff --git a/awx/network_ui_test/routing.py b/awx/network_ui_test/routing.py new file mode 100644 index 0000000000..b4ba915891 --- /dev/null +++ b/awx/network_ui_test/routing.py @@ -0,0 +1,12 @@ +# Copyright (c) 2017 Red Hat, Inc +from channels.routing import route +from awx.network_ui_test.consumers import ws_connect, ws_message, ws_disconnect, test_persistence + + +channel_routing = [ + route("websocket.connect", ws_connect, path=r"^/network_ui/test"), + route("websocket.receive", ws_message, path=r"^/network_ui/test"), + route("websocket.disconnect", ws_disconnect, path=r"^/network_ui/test"), + route("test_persistence", test_persistence.handle), +] + diff --git a/awx/network_ui/templates/network_ui/upload_test.html b/awx/network_ui_test/templates/network_ui_test/upload_test.html similarity index 50% rename from awx/network_ui/templates/network_ui/upload_test.html rename to awx/network_ui_test/templates/network_ui_test/upload_test.html index 4b23655229..f2a851b1c9 100644 --- a/awx/network_ui/templates/network_ui/upload_test.html +++ b/awx/network_ui_test/templates/network_ui_test/upload_test.html @@ -1,4 +1,4 @@ -
+ {% csrf_token %} {{form}} diff --git a/awx/network_ui_test/tools/Makefile b/awx/network_ui_test/tools/Makefile new file mode 100644 index 0000000000..c736a388b9 --- /dev/null +++ b/awx/network_ui_test/tools/Makefile @@ -0,0 +1,13 @@ + +SERVER = "https://meganuke:8043" +PORT = "9000" + +.PHONY: clean coverage + +clean: + git clean -fdX . + git clean -fd . + +coverage: + ./coverage_report.py ${SERVER} + python -m SimpleHTTPServer ${PORT} diff --git a/awx/network_ui/tools/coverage_report.py b/awx/network_ui_test/tools/coverage_report.py similarity index 97% rename from awx/network_ui/tools/coverage_report.py rename to awx/network_ui_test/tools/coverage_report.py index 05b784da1d..4eaccccee3 100755 --- a/awx/network_ui/tools/coverage_report.py +++ b/awx/network_ui_test/tools/coverage_report.py @@ -18,7 +18,7 @@ import subprocess logger = logging.getLogger('coverage_report') -TESTS_API = '/network_ui/tests' +TESTS_API = '/network_ui_test/tests' diff --git a/awx/network_ui_test/tools/requirements.txt b/awx/network_ui_test/tools/requirements.txt new file mode 100644 index 0000000000..bfe4ae8612 --- /dev/null +++ b/awx/network_ui_test/tools/requirements.txt @@ -0,0 +1,2 @@ +requests +docopt diff --git a/awx/network_ui_test/urls.py b/awx/network_ui_test/urls.py new file mode 100644 index 0000000000..0a432d163b --- /dev/null +++ b/awx/network_ui_test/urls.py @@ -0,0 +1,14 @@ +# Copyright (c) 2017 Red Hat, Inc +from django.conf.urls import url + +from awx.network_ui_test import views + +app_name = 'network_ui_test' +urlpatterns = [ + url(r'^tests$', views.tests, name='tests'), + url(r'^upload_test$', views.upload_test, name='upload_test'), + url(r'^download_coverage/(?P[0-9]+)$', views.download_coverage, name='download_coverage'), + url(r'^download_trace$', views.download_trace, name='download_trace'), + url(r'^download_recording$', views.download_recording, name='download_recording'), +] + diff --git a/awx/network_ui_test/views.py b/awx/network_ui_test/views.py new file mode 100644 index 0000000000..bdb612d224 --- /dev/null +++ b/awx/network_ui_test/views.py @@ -0,0 +1,112 @@ +# Copyright (c) 2017 Red Hat, Inc +from django.shortcuts import render +from django import forms +from django.http import JsonResponse, HttpResponse, HttpResponseRedirect +from django.core.exceptions import ObjectDoesNotExist +import yaml + +import json + + +# Create your views here. +from .models import FSMTrace, EventTrace, TopologySnapshot +from .models import TestCase, TestResult, Coverage + + +class FSMTraceForm(forms.Form): + topology_id = forms.IntegerField() + trace_id = forms.IntegerField() + client_id = forms.IntegerField() + + +def download_trace(request): + form = FSMTraceForm(request.GET) + if form.is_valid(): + topology_id = form.cleaned_data['topology_id'] + trace_id = form.cleaned_data['trace_id'] + client_id = form.cleaned_data['client_id'] + data = list(FSMTrace.objects.filter(trace_session_id=trace_id, + client_id=client_id).order_by('order').values()) + response = HttpResponse(yaml.safe_dump(data, default_flow_style=False), + content_type="application/force-download") + response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id) + return response + else: + return HttpResponse(form.errors) + + +class RecordingForm(forms.Form): + topology_id = forms.IntegerField() + trace_id = forms.IntegerField() + client_id = forms.IntegerField() + + +def download_recording(request): + form = RecordingForm(request.GET) + if form.is_valid(): + topology_id = form.cleaned_data['topology_id'] + trace_id = form.cleaned_data['trace_id'] + client_id = form.cleaned_data['client_id'] + data = dict() + data['event_trace'] = [json.loads(x) for x in EventTrace + .objects.filter(trace_session_id=trace_id, client_id=client_id) + .order_by('message_id') + .values_list('event_data', flat=True)] + data['fsm_trace'] = list(FSMTrace + .objects + .filter(trace_session_id=trace_id, client_id=client_id) + .order_by('order') + .values()) + data['snapshots'] = [json.loads(x) for x in TopologySnapshot + .objects.filter(trace_session_id=trace_id, client_id=client_id) + .order_by('order') + .values_list('snapshot_data', flat=True)] + response = HttpResponse(json.dumps(data, sort_keys=True, indent=4), + content_type="application/force-download") + response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id) + return response + else: + return HttpResponse(form.errors) + + +def tests(request): + tests = list(TestCase.objects.all().values('test_case_id', 'name')) + for x in tests: + x['coverage'] = "/network_ui_test/download_coverage/{0}".format(x['test_case_id']) + return JsonResponse(dict(tests=tests)) + + +def create_test(name, data): + try: + test_case = TestCase.objects.get(name=name) + test_case.test_case_data=json.dumps(data) + test_case.save() + except ObjectDoesNotExist: + TestCase(name=name, test_case_data=json.dumps(data)).save() + + +class UploadTestForm(forms.Form): + name = forms.CharField() + file = forms.FileField() + + +def upload_test(request): + if request.method == 'POST': + form = UploadTestForm(request.POST, request.FILES) + if form.is_valid(): + name = form.cleaned_data['name'] + data = json.loads(request.FILES['file'].read()) + create_test(name, data) + return HttpResponseRedirect('/network_ui_test/tests') + else: + form = UploadTestForm() + return render(request, 'network_ui_test/upload_test.html', {'form': form}) + + +def download_coverage(request, pk): + latest_tr = TestResult.objects.filter(test_case_id=pk).order_by('-time')[0] + coverage = Coverage.objects.get(test_result_id=latest_tr.pk) + response = HttpResponse(coverage.coverage_data, + content_type="application/json") + return response + diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 600dabf341..919e471a86 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -280,6 +280,7 @@ INSTALLED_APPS = ( 'awx.sso', 'solo', 'awx.network_ui', + 'awx.network_ui_test', ) INTERNAL_IPS = ('127.0.0.1',) diff --git a/awx/ui/client/src/network-ui/messages.js b/awx/ui/client/src/network-ui/messages.js index d2b4009f67..abf68e22e0 100644 --- a/awx/ui/client/src/network-ui/messages.js +++ b/awx/ui/client/src/network-ui/messages.js @@ -196,12 +196,14 @@ function StopRecording(sender, trace_id) { } exports.StopRecording = StopRecording; -function ViewPort(sender, scale, panX, panY, trace_id) { +function ViewPort(sender, scale, panX, panY, graph_width, graph_height, trace_id) { this.msg_type = "ViewPort"; this.sender = sender; this.scale = scale; this.panX = panX; this.panY = panY; + this.graph_width = graph_width; + this.graph_height = graph_height; this.trace_id = trace_id; } exports.ViewPort = ViewPort; diff --git a/awx/ui/client/src/network-ui/move.fsm.js b/awx/ui/client/src/network-ui/move.fsm.js index c1f9946bf5..9a66cd3ca2 100644 --- a/awx/ui/client/src/network-ui/move.fsm.js +++ b/awx/ui/client/src/network-ui/move.fsm.js @@ -378,7 +378,6 @@ _Move.prototype.onMouseMove = function (controller) { var i = 0; var j = 0; var previous_x, previous_y; - var membership_old_new; for (i = 0; i < devices.length; i++) { previous_x = devices[i].x; previous_y = devices[i].y; diff --git a/awx/ui/client/src/network-ui/network.ui.controller.js b/awx/ui/client/src/network-ui/network.ui.controller.js index a468172688..6b8dfef08c 100644 --- a/awx/ui/client/src/network-ui/network.ui.controller.js +++ b/awx/ui/client/src/network-ui/network.ui.controller.js @@ -1,7 +1,6 @@ /* Copyright (c) 2017 Red Hat, Inc. */ var angular = require('angular'); var fsm = require('./fsm.js'); -var null_fsm = require('./null.fsm.js'); var mode_fsm = require('./mode.fsm.js'); var hotkeys = require('./hotkeys.fsm.js'); var toolbox_fsm = require('./toolbox.fsm.js'); @@ -57,6 +56,9 @@ var NetworkUIController = function($scope, $scope.control_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/topology?inventory_id=" + $scope.inventory_id, null, {debug: false, reconnectInterval: 300}); + $scope.test_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/test?inventory_id=" + $scope.inventory_id, + null, + {debug: false, reconnectInterval: 300}); } else { $scope.control_socket = { on_message: util.noop @@ -65,6 +67,7 @@ var NetworkUIController = function($scope, $scope.my_location = $location.protocol() + "://" + $location.host() + ':' + $location.port(); $scope.history = []; $scope.client_id = 0; + $scope.test_client_id = 0; $scope.onMouseDownResult = ""; $scope.onMouseUpResult = ""; $scope.onMouseEnterResult = ""; @@ -91,7 +94,6 @@ var NetworkUIController = function($scope, $scope.selected_items = []; $scope.new_link = null; $scope.new_stream = null; - $scope.new_group_type = null; $scope.last_key = ""; $scope.last_key_code = null; $scope.last_event = null; @@ -108,9 +110,7 @@ var NetworkUIController = function($scope, $scope.MIN_ZOOM = 0.1; $scope.device_id_seq = util.natural_numbers(0); $scope.link_id_seq = util.natural_numbers(0); - $scope.group_id_seq = util.natural_numbers(0); $scope.message_id_seq = util.natural_numbers(0); - $scope.stream_id_seq = util.natural_numbers(0); $scope.test_result_id_seq = util.natural_numbers(0); $scope.animation_id_seq = util.natural_numbers(0); $scope.overall_toolbox_collapsed = false; @@ -180,13 +180,13 @@ var NetworkUIController = function($scope, if (!$scope.recording) { return; } - message.sender = $scope.client_id; + message.sender = $scope.test_client_id; message.trace_id = $scope.trace_id; message.message_id = $scope.message_id_seq(); var data = messages.serialize(message); if (!$scope.disconnected) { try { - $scope.control_socket.send(data); + $scope.test_socket.send(data); } catch(err) { $scope.initial_messages.push(message); @@ -196,15 +196,15 @@ var NetworkUIController = function($scope, $scope.onKeyDown = function ($event) { if ($scope.recording) { - $scope.send_control_message(new messages.KeyEvent($scope.client_id, - $event.key, - $event.keyCode, - $event.type, - $event.altKey, - $event.shiftKey, - $event.ctrlKey, - $event.metaKey, - $scope.trace_id)); + $scope.send_test_message(new messages.KeyEvent($scope.test_client_id, + $event.key, + $event.keyCode, + $event.type, + $event.altKey, + $event.shiftKey, + $event.ctrlKey, + $event.metaKey, + $scope.trace_id)); } $scope.last_event = $event; $scope.last_key = $event.key; @@ -215,7 +215,6 @@ var NetworkUIController = function($scope, }; //Define the FSMs - $scope.null_controller = new fsm.FSMController($scope, "null_fsm", null_fsm.Start, $scope); $scope.hotkeys_controller = new fsm.FSMController($scope, "hotkeys_fsm", hotkeys.Start, $scope); $scope.keybindings_controller = new fsm.FSMController($scope, "keybindings_fsm", keybindings.Start, $scope); $scope.view_controller = new fsm.FSMController($scope, "view_fsm", view.Start, $scope); @@ -325,9 +324,13 @@ var NetworkUIController = function($scope, $scope.first_channel = new fsm.Channel(null, - $scope.test_controller, + $scope.mode_controller, $scope); + $scope.test_channel = new fsm.Channel(null, + $scope.test_controller, + $scope); + var getMouseEventResult = function (mouseEvent) { return "(" + mouseEvent.x + ", " + mouseEvent.y + ")"; }; @@ -508,7 +511,7 @@ var NetworkUIController = function($scope, $scope.onMouseDown = function ($event) { $scope.normalize_mouse_event($event); if ($scope.recording) { - $scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id)); + $scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id)); } $scope.last_event = $event; $scope.first_channel.send('MouseDown', $event); @@ -519,7 +522,7 @@ var NetworkUIController = function($scope, $scope.onMouseUp = function ($event) { $scope.normalize_mouse_event($event); if ($scope.recording) { - $scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id)); + $scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id)); } $scope.last_event = $event; $scope.first_channel.send('MouseUp', $event); @@ -530,7 +533,7 @@ var NetworkUIController = function($scope, $scope.onMouseLeave = function ($event) { $scope.normalize_mouse_event($event); if ($scope.recording) { - $scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id)); + $scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id)); } $scope.onMouseLeaveResult = getMouseEventResult($event); $scope.cursor.hidden = true; @@ -540,7 +543,7 @@ var NetworkUIController = function($scope, $scope.onMouseMove = function ($event) { $scope.normalize_mouse_event($event); if ($scope.recording) { - $scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id)); + $scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id)); } //var coords = getCrossBrowserElementCoords($event); $scope.cursor.hidden = false; @@ -557,7 +560,7 @@ var NetworkUIController = function($scope, $scope.onMouseOver = function ($event) { $scope.normalize_mouse_event($event); if ($scope.recording) { - $scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id)); + $scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id)); } $scope.onMouseOverResult = getMouseEventResult($event); $scope.cursor.hidden = false; @@ -572,7 +575,7 @@ var NetworkUIController = function($scope, var deltaX = $event.deltaX; var deltaY = $event.deltaY; if ($scope.recording) { - $scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey, $scope.trace_id)); + $scope.send_test_message(new messages.MouseWheelEvent($scope.test_client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey, $scope.trace_id)); } $scope.last_event = $event; $scope.first_channel.send('MouseWheel', [$event, delta, deltaX, deltaY]); @@ -679,12 +682,12 @@ var NetworkUIController = function($scope, links[i].remote_selected = false; $scope.links.splice(index, 1); $scope.send_control_message(new messages.LinkDestroy($scope.client_id, - links[i].id, - links[i].from_device.id, - links[i].to_device.id, - links[i].from_interface.id, - links[i].to_interface.id, - links[i].name)); + links[i].id, + links[i].from_device.id, + links[i].to_device.id, + links[i].from_interface.id, + links[i].to_interface.id, + links[i].name)); } } for (i = 0; i < devices.length; i++) { @@ -693,12 +696,12 @@ var NetworkUIController = function($scope, $scope.devices.splice(index, 1); $scope.$emit('removeSearchOption', devices[i]); $scope.send_control_message(new messages.DeviceDestroy($scope.client_id, - devices[i].id, - devices[i].x, - devices[i].y, - devices[i].name, - devices[i].type, - devices[i].host_id)); + devices[i].id, + devices[i].x, + devices[i].y, + devices[i].name, + devices[i].type, + devices[i].host_id)); } for (j = 0; j < all_links.length; j++) { if (all_links[j].to_device === devices[i] || @@ -823,38 +826,30 @@ var NetworkUIController = function($scope, $scope.jump_to_animation(v_center.x, v_center.y, scale, false); }); - $scope.onDeployButton = function () { - $scope.send_control_message(new messages.Deploy($scope.client_id)); - }; - - $scope.onDestroyButton = function () { - $scope.send_control_message(new messages.Destroy($scope.client_id)); - }; - $scope.onRecordButton = function () { $scope.recording = ! $scope.recording; if ($scope.recording) { $scope.trace_id = $scope.trace_id_seq(); - $scope.send_control_message(new messages.MultipleMessage($scope.client_id, - [new messages.StartRecording($scope.client_id, $scope.trace_id), - new messages.ViewPort($scope.client_id, - $scope.current_scale, - $scope.panX, - $scope.panY, - $scope.trace_id), - new messages.Snapshot($scope.client_id, - $scope.devices, - $scope.links, - 0, - $scope.trace_id)])); + $scope.send_test_message(new messages.MultipleMessage($scope.test_client_id, + [new messages.StartRecording($scope.test_client_id, $scope.trace_id), + new messages.ViewPort($scope.test_client_id, + $scope.current_scale, + $scope.panX, + $scope.panY, + $scope.trace_id), + new messages.Snapshot($scope.test_client_id, + $scope.devices, + $scope.links, + 0, + $scope.trace_id)])); } else { - $scope.send_control_message(new messages.MultipleMessage($scope.client_id, - [new messages.Snapshot($scope.client_id, - $scope.devices, - $scope.links, - 1, - $scope.trace_id), - new messages.StopRecording($scope.client_id, $scope.trace_id)])); + $scope.send_test_message(new messages.MultipleMessage($scope.test_client_id, + [new messages.Snapshot($scope.test_client_id, + $scope.devices, + $scope.links, + 1, + $scope.trace_id), + new messages.StopRecording($scope.test_client_id, $scope.trace_id)])); } }; @@ -909,7 +904,7 @@ var NetworkUIController = function($scope, $scope.test_results = []; $scope.current_tests = $scope.tests.slice(); - $scope.first_channel.send("EnableTest", new messages.EnableTest()); + $scope.test_channel.send("EnableTest", new messages.EnableTest()); }; $scope.all_buttons = []; @@ -961,35 +956,6 @@ var NetworkUIController = function($scope, $scope.devices.push(device); }; - $scope.onGroupCreate = function(data) { - $scope.create_group(data); - }; - - $scope.create_group = function(data) { - var group = new models.Group(data.id, - data.name, - data.type, - data.x1, - data.y1, - data.x2, - data.y2, - false); - $scope.group_id_seq = util.natural_numbers(data.id); - $scope.groups.push(group); - }; - - $scope.breadcrumbGroups = function(){ - let breadcrumbGroups = []; - for(var i = 0; i < $scope.groups.length; i++){ - let group = $scope.groups[i]; - if(group.is_in_breadcrumb($scope.view_port)){ - group.distance = util.distance(group.x1, group.y1, group.x2, group.y2); - breadcrumbGroups.push(group); - } - } - return breadcrumbGroups; - }; - $scope.forDevice = function(device_id, data, fn) { var i = 0; for (i = 0; i < $scope.devices.length; i++) { @@ -1025,16 +991,6 @@ var NetworkUIController = function($scope, } }; - $scope.forGroup = function(group_id, data, fn) { - var i = 0; - for (i = 0; i < $scope.groups.length; i++) { - if ($scope.groups[i].id === group_id) { - fn($scope.groups[i], data); - break; - } - } - }; - $scope.onDeviceLabelEdit = function(data) { $scope.edit_device_label(data); }; @@ -1201,88 +1157,6 @@ var NetworkUIController = function($scope, } }; - $scope.onGroupLabelEdit = function(data) { - $scope.edit_group_label(data); - }; - - $scope.edit_group_label = function(data) { - $scope.forGroup(data.id, data, function(group, data) { - group.name = data.name; - }); - }; - - $scope.redo = function(type_data) { - var type = type_data[0]; - var data = type_data[1]; - - if (type === "DeviceMove") { - $scope.move_device(data); - } - - if (type === "DeviceCreate") { - $scope.create_device(data); - } - - if (type === "DeviceDestroy") { - $scope.destroy_device(data); - } - - if (type === "DeviceLabelEdit") { - $scope.edit_device_label(data); - } - - if (type === "LinkCreate") { - $scope.create_link(data); - } - - if (type === "LinkDestroy") { - $scope.destroy_link(data); - } - }; - - - $scope.undo = function(type_data) { - var type = type_data[0]; - var data = type_data[1]; - var inverted_data; - - if (type === "DeviceMove") { - inverted_data = angular.copy(data); - inverted_data.x = data.previous_x; - inverted_data.y = data.previous_y; - $scope.move_device(inverted_data); - } - - if (type === "DeviceCreate") { - $scope.destroy_device(data); - } - - if (type === "DeviceDestroy") { - inverted_data = new messages.DeviceCreate(data.sender, - data.id, - data.previous_x, - data.previous_y, - data.previous_name, - data.previous_type, - data.previous_host_id); - $scope.create_device(inverted_data); - } - - if (type === "DeviceLabelEdit") { - inverted_data = angular.copy(data); - inverted_data.name = data.previous_name; - $scope.edit_device_label(inverted_data); - } - - if (type === "LinkCreate") { - $scope.destroy_link(data); - } - - if (type === "LinkDestroy") { - $scope.create_link(data); - } - }; - $scope.onClientId = function(data) { $scope.client_id = data; $scope.send_initial_messages(); @@ -1295,7 +1169,6 @@ var NetworkUIController = function($scope, $scope.current_scale = data.scale; $scope.$emit('awxNet-UpdateZoomWidget', $scope.current_scale, true); $scope.link_id_seq = util.natural_numbers(data.link_id_seq); - $scope.group_id_seq = util.natural_numbers(data.group_id_seq); $scope.device_id_seq = util.natural_numbers(data.device_id_seq); }; @@ -1326,99 +1199,11 @@ var NetworkUIController = function($scope, } }; - $scope.onToolboxItem = function (data) { - if (data.toolbox_name === "Site") { - var site = util.parse_variables(data.data); - var i = 0; - var j = 0; - var site_copy = new models.Group(site.id, - site.name, - site.type, - site.x1, - site.y1, - site.x2, - site.y2, - false); - var device, device_copy; - var process, process_copy; - var intf, intf_copy; - var device_map = {}; - for (i = 0; i < site.devices.length; i++) { - device = site.devices[i]; - device_copy = new models.Device(device.id, - device.name, - device.x, - device.y, - device.type); - device_map[device.id] = device_copy; - device_copy.interface_map = {}; - site_copy.devices.push(device_copy); - for(j=0; j < device.interfaces.length; j++) { - intf = device.interfaces[j]; - intf_copy = new models.Interface(intf.id, intf.name); - intf_copy.device = device_copy; - device_copy.interfaces.push(intf_copy); - device_copy.interface_map[intf.id] = intf_copy; - } - for(j=0; j < device.processes.length; j++) { - process = device.processes[j]; - process_copy = new models.Process(process.id, - process.name, - process.type, - process.x, - process.y); - process_copy.device = device; - device_copy.processes.push(process_copy); - } - } - var group, group_copy; - for (i = 0; i < site.groups.length; i++) { - group = site.groups[i]; - group_copy = new models.Group(group.id, - group.name, - group.type, - group.x1, - group.y1, - group.x2, - group.y2, - false); - site_copy.groups.push(group_copy); - } - var link, link_copy; - for (i = 0; i < site.links.length; i++) { - link = site.links[i]; - link_copy = new models.Link(link.id, - device_map[link.from_device_id], - device_map[link.to_device_id], - device_map[link.from_device_id].interface_map[link.from_interface_id], - device_map[link.to_device_id].interface_map[link.to_interface_id]); - link_copy.name = link.name; - device_map[link.from_device_id].interface_map[link.from_interface_id].link = link_copy; - device_map[link.to_device_id].interface_map[link.to_interface_id].link = link_copy; - site_copy.links.push(link_copy); - } - - var stream, stream_copy; - - for(i = 0; i < site.streams.length;i++) { - stream = site.streams[i]; - stream_copy = new models.Stream(stream.id, - device_map[stream.from_device], - device_map[stream.to_device], - stream.label); - site_copy.streams.push(stream_copy); - } - $scope.site_toolbox.items.push(site_copy); - } - }; - $scope.onSnapshot = function (data) { //Erase the existing state $scope.devices = []; $scope.links = []; - $scope.groups = []; - $scope.streams = []; var device_map = {}; var device_interface_map = {}; @@ -1430,17 +1215,11 @@ var NetworkUIController = function($scope, var new_intf = null; var max_device_id = null; var max_link_id = null; - var max_group_id = null; - var max_stream_id = null; var min_x = null; var min_y = null; var max_x = null; var max_y = null; var new_link = null; - var new_group = null; - var process = null; - var new_process = null; - var new_stream = null; //Build the devices for (i = 0; i < data.devices.length; i++) { @@ -1478,16 +1257,6 @@ var NetworkUIController = function($scope, $scope.devices.push(new_device); device_map[device.id] = new_device; device_interface_map[device.id] = {}; - for (j = 0; j < device.processes.length; j++) { - process = device.processes[j]; - new_process = (new models.Process(process.id, - process.name, - process.process_type, - 0, - 0)); - new_process.device = new_device; - new_device.processes.push(new_process); - } for (j = 0; j < device.interfaces.length; j++) { intf = device.interfaces[j]; new_intf = (new models.Interface(intf.id, @@ -1516,50 +1285,6 @@ var NetworkUIController = function($scope, device_interface_map[link.to_device_id][link.to_interface_id].link = new_link; } - //Build the streams - var stream = null; - for (i = 0; i < data.streams.length; i++) { - stream = data.streams[i]; - if (max_stream_id === null || stream.id > max_stream_id) { - max_stream_id = stream.id; - } - new_stream = new models.Stream(stream.id, - device_map[stream.from_id], - device_map[stream.to_id], - stream.label); - $scope.streams.push(new_stream); - } - - //Build the groups - var group = null; - for (i = 0; i < data.groups.length; i++) { - group = data.groups[i]; - if (max_group_id === null || group.id > max_group_id) { - max_group_id = group.id; - } - new_group = new models.Group(group.id, - group.name, - group.group_type, - group.x1, - group.y1, - group.x2, - group.y2, - false); - new_group.group_id = group.inventory_group_id; - if (group.members !== undefined) { - for (j=0; j < group.members.length; j++) { - new_group.devices.push(device_map[group.members[j]]); - } - } - $scope.groups.push(new_group); - } - - //Update group membership - - for (i = 0; i < $scope.groups.length; i++) { - $scope.groups[i].update_membership($scope.devices, $scope.groups); - } - var diff_x; var diff_y; @@ -1592,18 +1317,9 @@ var NetworkUIController = function($scope, if (max_link_id !== null) { $scope.link_id_seq = util.natural_numbers(max_link_id); } - //Update the stream_id_seq to be greater than all stream ids to prevent duplicate ids. - if (max_stream_id !== null) { - $scope.stream_id_seq = util.natural_numbers(max_stream_id); - } - //Update the group_id_seq to be greater than all group ids to prevent duplicate ids. - if (max_group_id !== null) { - $scope.group_id_seq = util.natural_numbers(max_group_id); - } $scope.updateInterfaceDots(); $scope.$emit('instatiateSelect', $scope.devices); - $scope.$emit('awxNet-breadcrumbGroups', $scope.breadcrumbGroups()); $scope.update_device_variables(); }; @@ -1656,6 +1372,15 @@ var NetworkUIController = function($scope, //ignore }; + $scope.test_socket.onmessage = function(message) { + $scope.test_channel.send('Message', message); + $scope.$apply(); + }; + + $scope.test_socket.onopen = function() { + //ignore + }; + $scope.send_initial_messages = function() { var i = 0; var messages_to_send = $scope.initial_messages; @@ -1664,9 +1389,9 @@ var NetworkUIController = function($scope, $scope.initial_messages = []; for(i = 0; i < messages_to_send.length; i++) { message = messages_to_send[i]; - message.sender = $scope.client_id; + message.sender = $scope.test_client_id; data = messages.serialize(message); - $scope.control_socket.send(data); + $scope.test_socket.send(data); } }; @@ -1674,6 +1399,25 @@ var NetworkUIController = function($scope, if ($scope.control_socket.readyState === WebSocket.OPEN) { $scope.control_socket.onopen(); } + // Call onopen directly if $scope.test_socket is already open + if ($scope.test_socket.readyState === WebSocket.OPEN) { + $scope.test_socket.onopen(); + } + + $scope.send_test_message = function (message) { + var i = 0; + message.sender = $scope.test_client_id; + message.message_id = $scope.message_id_seq(); + if (message.constructor.name === "MultipleMessage") { + for (i=0; i < message.messages.length; i++) { + message.messages[i].message_id = $scope.message_id_seq(); + } + } + var data = messages.serialize(message); + if (!$scope.disconnected) { + $scope.test_socket.send(data); + } + }; $scope.send_control_message = function (message) { var i = 0; @@ -1811,13 +1555,10 @@ var NetworkUIController = function($scope, $scope.hide_buttons = false; $scope.hide_links = false; $scope.hide_interfaces = false; - $scope.hide_groups = false; }; $scope.reset_fsm_state = function () { - $scope.null_controller.state = null_fsm.Start; - $scope.null_controller.state.start($scope.null_controller); $scope.hotkeys_controller.state = hotkeys.Start; $scope.hotkeys_controller.state.start($scope.hotkeys_controller); $scope.keybindings_controller.state = keybindings.Start; diff --git a/awx/ui/client/src/network-ui/null.fsm.js b/awx/ui/client/src/network-ui/null.fsm.js deleted file mode 100644 index 528efbcf31..0000000000 --- a/awx/ui/client/src/network-ui/null.fsm.js +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright (c) 2017 Red Hat, Inc. */ -var inherits = require('inherits'); -var fsm = require('./fsm.js'); - -function _State () { -} -inherits(_State, fsm._State); - - -function _Start () { - this.name = 'Start'; -} -inherits(_Start, _State); -var Start = new _Start(); -exports.Start = Start; - -function _Ready () { - this.name = 'Ready'; -} -inherits(_Ready, _State); -var Ready = new _Ready(); -exports.Ready = Ready; - - - - -_Start.prototype.start = function (controller) { - - controller.changeState(Ready); - -}; -_Start.prototype.start.transitions = ['Ready']; - - - - diff --git a/awx/ui/client/src/network-ui/test.fsm.js b/awx/ui/client/src/network-ui/test.fsm.js index ebd842d15b..85039e1f84 100644 --- a/awx/ui/client/src/network-ui/test.fsm.js +++ b/awx/ui/client/src/network-ui/test.fsm.js @@ -51,7 +51,31 @@ var Reporting = new _Reporting(); exports.Reporting = Reporting; +_State.prototype.onMessage = function(controller, msg_type, message) { + var type_data = JSON.parse(message.data); + var type = type_data[0]; + var data = type_data[1]; + + controller.handle_message(type, data); +}; + +_State.prototype.onid = function(controller, msg_type, message) { + controller.scope.test_client_id = message; +}; + +_State.prototype.onTestCase = function(controller, msg_type, message) { + if ('runnable' in message[1]) { + if (!message[1].runnable) { + return; + } + } + controller.scope.tests.push(new models.Test(message[0], + message[1].event_trace, + [], + message[1].snapshots[0], + message[1].snapshots[1])); +}; _Disabled.prototype.onEnableTest = function (controller) { @@ -93,15 +117,15 @@ _Reporting.prototype.start = function (controller) { controller.scope.version); controller.scope.test_results.push(test_result); console.log(["Reporting test", test_result.name, test_result.id]); - controller.scope.send_control_message(new messages.TestResult(controller.scope.client_id, - test_result.id, - test_result.name, - test_result.result, - test_result.date, - test_result.code_under_test)); + controller.scope.send_test_message(new messages.TestResult(controller.scope.client_id, + test_result.id, + test_result.name, + test_result.result, + test_result.date, + test_result.code_under_test)); if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) { console.log(["Reporting coverage", test_result.name, test_result.id]); - controller.scope.send_control_message(new messages.Coverage(controller.scope.client_id, window.__coverage__, test_result.id)); + controller.scope.send_test_message(new messages.Coverage(controller.scope.client_id, window.__coverage__, test_result.id)); } controller.changeState(Loading); }; @@ -144,15 +168,15 @@ _Ready.prototype.start = function (controller) { var load_id = controller.scope.test_result_id_seq(); console.log(["Reporting Load", load_id]); - controller.scope.send_control_message(new messages.TestResult(controller.scope.client_id, - load_id, - "Load", - "passed", - new Date().toISOString(), - controller.scope.version)); + controller.scope.send_test_message(new messages.TestResult(controller.scope.client_id, + load_id, + "Load", + "passed", + new Date().toISOString(), + controller.scope.version)); if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) { console.log(["Reporting Load Coverage", load_id]); - controller.scope.send_control_message(new messages.Coverage(controller.scope.client_id, window.__coverage__, load_id)); + controller.scope.send_test_message(new messages.Coverage(controller.scope.client_id, window.__coverage__, load_id)); } controller.changeState(Loading); diff --git a/awx/ui/client/src/network-ui/time.fsm.js b/awx/ui/client/src/network-ui/time.fsm.js index c36edeca7c..86ad3bc06e 100644 --- a/awx/ui/client/src/network-ui/time.fsm.js +++ b/awx/ui/client/src/network-ui/time.fsm.js @@ -2,7 +2,6 @@ var inherits = require('inherits'); var fsm = require('./fsm.js'); var util = require('./util.js'); -var models = require('./models.js'); function _State () { } @@ -176,6 +175,13 @@ _Present.prototype.onViewPort = function(controller, msg_type, message) { controller.scope.current_scale = message.scale; controller.scope.panX = message.panX; controller.scope.panY = message.panY; + if (message.graph_width !== undefined) { + controller.scope.graph.width = message.graph_width; + } + if (message.graph_height !== undefined) { + controller.scope.graph.height = message.graph_height; + } + controller.scope.update_toolbox_heights(); controller.scope.updateScaledXY(); controller.scope.updatePanAndScale(); }; @@ -224,19 +230,36 @@ _Present.prototype.onMouseWheelEvent = function(controller, msg_type, message) { } }; -_Present.prototype.onTestCase = function(controller, msg_type, message) { - if ('runnable' in message[1]) { - if (!message[1].runnable) { - return; - } - } - controller.scope.tests.push(new models.Test(message[0], - message[1].event_trace, - [], - message[1].snapshots[0], - message[1].snapshots[1])); +_Present.prototype.onRecordButton = function(controller) { + controller.scope.onRecordButton(); }; +_Present.prototype.onExportButton = function(controller) { + controller.scope.onExportButton(); +}; + +_Present.prototype.onExportYamlButton = function(controller) { + controller.scope.onExportYamlButton(); +}; + +_Present.prototype.onDownloadTraceButton = function(controller) { + controller.scope.onDownloadTraceButton(); +}; + +_Present.prototype.onDownloadRecordingButton = function(controller) { + controller.scope.onDownloadRecordingButton(); +}; + +_Present.prototype.onNoop = function(controller, msg_type, message) { + +}; + +_Present.prototype.onTestCompleted = function(controller, msg_type, message) { + + controller.scope.test_channel.send(msg_type, message); +} + + _Present.prototype.onError = function(controller, msg_type, message) { throw new Error("ServerError: " + message); }; diff --git a/awx/ui/client/src/network-ui/view.fsm.js b/awx/ui/client/src/network-ui/view.fsm.js index b88e073bdc..626101d6cb 100644 --- a/awx/ui/client/src/network-ui/view.fsm.js +++ b/awx/ui/client/src/network-ui/view.fsm.js @@ -85,7 +85,6 @@ _Scale.prototype.onMouseWheel = function (controller, msg_type, message) { var item = controller.scope.context_menus[0]; item.enabled = false; controller.scope.$emit('awxNet-UpdateZoomWidget', controller.scope.current_scale, true); - controller.scope.$emit('awxNet-breadcrumbGroups', controller.scope.breadcrumbGroups()); controller.scope.updatePanAndScale(); controller.changeState(Ready); };