diff --git a/awx/network_ui/admin.py b/awx/network_ui/admin.py index 3154699382..a1bb367306 100644 --- a/awx/network_ui/admin.py +++ b/awx/network_ui/admin.py @@ -36,6 +36,20 @@ from awx.network_ui.models import FSMTrace from awx.network_ui.models import TopologyInventory +from awx.network_ui.models import EventTrace + +from awx.network_ui.models import Coverage + +from awx.network_ui.models import TopologySnapshot + +from awx.network_ui.models import TestCase + +from awx.network_ui.models import Result + +from awx.network_ui.models import CodeUnderTest + +from awx.network_ui.models import TestResult + class DeviceAdmin(admin.ModelAdmin): fields = ('topology', 'name', 'x', 'y', 'id', 'type', 'interface_id_seq', 'process_id_seq', 'host_id',) @@ -179,3 +193,59 @@ class TopologyInventoryAdmin(admin.ModelAdmin): admin.site.register(TopologyInventory, TopologyInventoryAdmin) + + +class EventTraceAdmin(admin.ModelAdmin): + fields = ('client', 'trace_session_id', 'event_data', 'message_id',) + raw_id_fields = ('client',) + + +admin.site.register(EventTrace, EventTraceAdmin) + + +class CoverageAdmin(admin.ModelAdmin): + fields = ('coverage_data', 'test_result',) + raw_id_fields = ('test_result',) + + +admin.site.register(Coverage, CoverageAdmin) + + +class TopologySnapshotAdmin(admin.ModelAdmin): + fields = ('client', 'topology_id', 'trace_session_id', 'snapshot_data', 'order',) + raw_id_fields = ('client', 'snapshot_data',) + + +admin.site.register(TopologySnapshot, TopologySnapshotAdmin) + + +class TestCaseAdmin(admin.ModelAdmin): + fields = ('name', 'test_case_data',) + raw_id_fields = ('name',) + + +admin.site.register(TestCase, TestCaseAdmin) + + +class ResultAdmin(admin.ModelAdmin): + fields = ('name',) + raw_id_fields = () + + +admin.site.register(Result, ResultAdmin) + + +class CodeUnderTestAdmin(admin.ModelAdmin): + fields = ('version_x', 'version_y', 'version_z', 'commits_since', 'commit_hash',) + raw_id_fields = ('code_under_test_id',) + + +admin.site.register(CodeUnderTest, CodeUnderTestAdmin) + + +class TestResultAdmin(admin.ModelAdmin): + fields = ('test_case', 'result', 'code_under_test', 'time', 'id', 'client',) + raw_id_fields = ('test_case', 'result', 'code_under_test', 'client',) + + +admin.site.register(TestResult, TestResultAdmin) diff --git a/awx/network_ui/consumers.py b/awx/network_ui/consumers.py index de5a0c262b..6b51847d2c 100644 --- a/awx/network_ui/consumers.py +++ b/awx/network_ui/consumers.py @@ -8,8 +8,9 @@ from awx.network_ui.models import GroupDevice as GroupDeviceMap from awx.network_ui.models import DataSheet, DataBinding, DataType from awx.network_ui.models import Process, Stream from awx.network_ui.models import Toolbox, ToolboxItem -from awx.network_ui.models import FSMTrace +from awx.network_ui.models import FSMTrace, EventTrace, Coverage, TopologySnapshot from awx.network_ui.models import TopologyInventory +from awx.network_ui.models import TestCase, TestResult, CodeUnderTest, Result from awx.network_ui.messages import MultipleMessage, InterfaceCreate, LinkCreate, to_dict import urlparse from django.core.exceptions import ObjectDoesNotExist @@ -18,14 +19,14 @@ from collections import defaultdict import math import random import logging +from django.utils.dateparse import parse_datetime + from awx.network_ui.utils import transform_dict import dpath.util from pprint import pformat -import os import json -import time # Connected to websocket.connect HISTORY_MESSAGE_IGNORE_TYPES = ['DeviceSelected', @@ -44,6 +45,10 @@ RACK_SPACING = 50 logger = logging.getLogger("awx.network_ui.consumers") +class NetworkUIException(Exception): + + pass + def circular_layout(topology_id): n = Device.objects.filter(topology_id=topology_id).count() @@ -281,7 +286,7 @@ class _Persistence(object): try: message_type_id = MessageType.objects.get(name=message_type).pk except ObjectDoesNotExist: - logger.warning("Unsupported message %s", message_type) + logger.warning("Unsupported message %s: no message type", message_type) return TopologyHistory(topology_id=topology_id, client_id=client_id, @@ -290,9 +295,19 @@ class _Persistence(object): message_data=message['text']).save() handler = self.get_handler(message_type) if handler is not None: - handler(message_value, topology_id, client_id) + 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", message_type) + logger.warning("Unsupported message %s: no handler", message_type) def get_handler(self, message_type): return getattr(self, "on{0}".format(message_type), None) @@ -454,12 +469,44 @@ class _Persistence(object): # grid_layout(topology_id) tier_layout(topology_id) + 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') + + print (xyz) + print (commits_since) + print (commit_hash) + + x, y, z = [int(i) for i in xyz.split('.')] + + print (x, y, z) + + 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) + + print (code_under_test) + + 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() + print (tr.pk) + + def onCoverage(self, coverage, topology_id, client_id): - with open(os.path.abspath("coverage/coverage{0}.json".format(int(time.time()))), "w") as f: - f.write(json.dumps(coverage['coverage'])) + 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 @@ -469,9 +516,10 @@ class _Persistence(object): def write_event(self, event, topology_id, client_id): if event.get('save', True): - with open(os.path.abspath("recording/recording_{0}.log".format(topology_id)), "a") as f: - f.write(json.dumps(event)) - f.write("\n") + 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 @@ -538,6 +586,13 @@ class _Persistence(object): 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() @@ -554,7 +609,7 @@ class _UndoPersistence(object): if handler is not None: handler(message_value, topology_id, client_id) else: - logger.warnding("Unsupported undo message %s", message_type) + logger.warning("Unsupported undo message %s", message_type) def onSnapshot(self, snapshot, topology_id, client_id): pass @@ -648,7 +703,7 @@ class _Discovery(object): if handler is not None: handler(message_value, topology_id) else: - logger.warning("Unsupported message %s", message_type) + logger.warning("Unsupported discovery message %s", message_type) def get_handler(self, message_type): return getattr(self, "on{0}".format(message_type), None) @@ -810,6 +865,7 @@ def ws_connect(message): 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])}) message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])}) topology_data = transform_dict(dict(topology_id='topology_id', @@ -825,6 +881,7 @@ def ws_connect(message): 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): @@ -834,6 +891,11 @@ def send_toolboxes(channel): 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) @@ -919,7 +981,8 @@ def ws_message(message): @channel_session def ws_disconnect(message): - Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel) + if 'topology_id' in message.channel_session: + Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel) def console_printer(message): diff --git a/awx/network_ui/designs/models.yml b/awx/network_ui/designs/models.yml index d1384fc837..e204679670 100644 --- a/awx/network_ui/designs/models.yml +++ b/awx/network_ui/designs/models.yml @@ -102,8 +102,8 @@ models: pk: true type: AutoField name: Client - x: -510 - y: 141 + x: -518 + y: 138 - fields: - name: topology_history_id pk: true @@ -365,6 +365,130 @@ models: name: TopologyInventory x: -226 y: -19 +- fields: + - name: event_trace_id + pk: true + type: AutoField + - name: client + ref: Client + ref_field: client_id + type: ForeignKey + - default: 0 + name: trace_session_id + type: IntegerField + - name: event_data + type: TextField + - name: message_id + type: IntegerField + name: EventTrace + x: -1087 + y: 202 +- fields: + - name: coverage_id + pk: true + type: AutoField + - name: coverage_data + type: TextField + - name: test_result + ref: TestResult + ref_field: test_result_id + type: ForeignKey + name: Coverage + x: -1068 + y: -4 +- fields: + - name: topology_snapshot_id + pk: true + type: AutoField + - name: client + ref: Client + ref_field: client_id + type: ForeignKey + - name: topology_id + type: IntegerField + - name: trace_session_id + type: IntegerField + - name: snapshot_data + ref: TopologySnapshot + ref_field: snapshot_data + type: TextField + - name: order + type: IntegerField + name: TopologySnapshot + x: -1123 + y: -277 +- fields: + - name: test_case_id + pk: true + type: AutoField + - len: 200 + name: name + ref: TestCase + ref_field: name + type: CharField + - name: test_case_data + type: TextField + name: TestCase + x: -1642 + y: -38 +- fields: + - name: result_id + pk: true + type: AutoField + - len: 20 + name: name + type: CharField + name: Result + x: -1610 + y: 120 +- fields: + - name: code_under_test_id + pk: true + ref: CodeUnderTest + ref_field: code_under_test_id + type: AutoField + - name: version_x + type: IntegerField + - name: version_y + type: IntegerField + - name: version_z + type: IntegerField + - name: commits_since + type: IntegerField + - len: 40 + name: commit_hash + type: CharField + name: CodeUnderTest + x: -1612 + y: 259 +- fields: + - name: test_result_id + pk: true + type: AutoField + - name: test_case + ref: TestCase + ref_field: test_case_id + type: ForeignKey + - name: result + ref: Result + ref_field: result_id + type: ForeignKey + - name: code_under_test + ref: CodeUnderTest + ref_field: code_under_test_id + type: ForeignKey + - name: time + type: DateTimeField + - default: 0 + name: id + type: IntegerField + - name: client + ref: Client + ref_field: client_id + type: ForeignKey + name: TestResult + x: -1336 + y: -49 modules: [] view: panX: 213.729555519212 diff --git a/awx/network_ui/management/commands/create_2_tier_clos_test_topology.py b/awx/network_ui/management/commands/create_2_tier_clos_test_topology.py index b0555086e9..e558b1a2f2 100644 --- a/awx/network_ui/management/commands/create_2_tier_clos_test_topology.py +++ b/awx/network_ui/management/commands/create_2_tier_clos_test_topology.py @@ -3,26 +3,22 @@ from django.core.management.base import BaseCommand from awx.network_ui.models import Topology, Device, Link, Interface from collections import defaultdict - - -def natural_numbers(): - i = 1 - while True: - yield i - i += 1 +from .util import natural_numbers class Command(BaseCommand): - help = '''Creates a 2 tier clos topology with n nodes in the 1st tier and m nodes - in the 2nd tier and h hosts per pair of switches''' + help = '''Adds a 2 tier clos topology with n nodes in the 1st tier and m nodes + in the 2nd tier and h hosts per pair of switches to the topology with id `id`''' def add_arguments(self, parser): + parser.add_argument('id', type=int) parser.add_argument('n', type=int) parser.add_argument('m', type=int) parser.add_argument('h', type=int) def handle(self, *args, **options): + topology_id = options['id'] n = options['n'] m = options['m'] h = options['h'] @@ -30,15 +26,15 @@ class Command(BaseCommand): print "n", n print "m", m - topology = Topology(name="test_{0}".format(n), scale=1.0, panX=0, panY=0) - topology.save() + topology = Topology.objects.get(pk=topology_id) devices = [] hosts_per_leaf = [] leaves = [] spines = [] - id_seq = natural_numbers() + id_seq = natural_numbers(topology.device_id_seq) + link_id_seq = natural_numbers(topology.link_id_seq) tier2 = 100 tier1 = 500 @@ -106,7 +102,8 @@ class Command(BaseCommand): link = Link(from_device=devices[leaf.id], to_device=devices[spine.id], from_interface=from_interface, - to_interface=to_interface) + to_interface=to_interface, + id=next(link_id_seq)) links.append(link) for i, hosts in enumerate(hosts_per_leaf): leaf1 = leaves[2 * i] @@ -125,7 +122,8 @@ class Command(BaseCommand): link = Link(from_device=devices[leaf1.id], to_device=devices[host.id], from_interface=from_interface, - to_interface=to_interface) + to_interface=to_interface, + id=next(link_id_seq)) links.append(link) from_interface = Interface(device=devices[leaf2.id], name="swp" + str(len(interfaces[leaf2.id]) + 1), @@ -140,9 +138,14 @@ class Command(BaseCommand): link = Link(from_device=devices[leaf2.id], to_device=devices[host.id], from_interface=from_interface, - to_interface=to_interface) + to_interface=to_interface, + id=next(link_id_seq)) links.append(link) Link.objects.bulk_create(links) + topology.device_id_seq = next(id_seq) + topology.link_id_seq = next(link_id_seq) + topology.save() + print "Topology: ", topology.pk diff --git a/awx/network_ui/management/commands/create_worst_case_test_topology.py b/awx/network_ui/management/commands/create_worst_case_test_topology.py index 4a2312ed6e..34eb5c9837 100644 --- a/awx/network_ui/management/commands/create_worst_case_test_topology.py +++ b/awx/network_ui/management/commands/create_worst_case_test_topology.py @@ -1,24 +1,29 @@ # Copyright (c) 2017 Red Hat, Inc from django.core.management.base import BaseCommand -from awx.network_ui.models import Topology, Device, Link +from awx.network_ui.models import Topology, Device, Link, Interface import math +from collections import defaultdict +from .util import natural_numbers class Command(BaseCommand): - help = 'Creates a fully connected topology with n nodes' + help = 'Adds a fully connected topology with n nodes to topology pk id' def add_arguments(self, parser): + parser.add_argument('id', type=int) parser.add_argument('n', type=int) def handle(self, *args, **options): + topology_id = options['id'] n = options['n'] - topology = Topology(name="test_{0}".format(n), scale=1.0, panX=0, panY=0) - topology.save() + topology = Topology.objects.get(topology_id=topology_id) + link_id_seq = natural_numbers(topology.link_id_seq) + device_id_seq = natural_numbers(topology.device_id_seq) devices = [] r = 1000 @@ -30,25 +35,39 @@ class Command(BaseCommand): for i in xrange(n): device = Device(name="R{0}".format(i), - x=math.cos(arc_radians*i)*r, - y=math.sin(arc_radians*i)*r, - id=i, + x=math.cos(arc_radians * i) * r, + y=math.sin(arc_radians * i) * r, + id=next(device_id_seq), type="router", topology_id=topology.pk) devices.append(device) Device.objects.bulk_create(devices) - devices = {x.id: x for x in Device.objects.filter(topology_id=topology.pk)} + devices = list(Device.objects.filter(topology_id=topology.pk)) links = [] + interfaces = defaultdict(list) for i in xrange(n): for j in xrange(i): if i == j: continue + from_interface = Interface(device=devices[i], + name="swp" + str(len(interfaces[i]) + 1), + id=(len(interfaces[i]) + 1)) + from_interface.save() + interfaces[i].append(from_interface) + to_interface = Interface(device=devices[j], + name="swp" + str(len(interfaces[j]) + 1), + id=(len(interfaces[j]) + 1)) + to_interface.save() + interfaces[j].append(to_interface) link = Link(from_device=devices[i], - to_device=devices[j]) + to_device=devices[j], + from_interface=from_interface, + to_interface=to_interface, + id=next(link_id_seq)) links.append(link) Link.objects.bulk_create(links) diff --git a/awx/network_ui/management/commands/util.py b/awx/network_ui/management/commands/util.py new file mode 100644 index 0000000000..8e28a44704 --- /dev/null +++ b/awx/network_ui/management/commands/util.py @@ -0,0 +1,5 @@ + +def natural_numbers(i=1): + while True: + yield i + i += 1 diff --git a/awx/network_ui/migrations/0027_eventtrace.py b/awx/network_ui/migrations/0027_eventtrace.py new file mode 100644 index 0000000000..05b65c0972 --- /dev/null +++ b/awx/network_ui/migrations/0027_eventtrace.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0026_auto_20180105_1403'), + ] + + operations = [ + migrations.CreateModel( + name='EventTrace', + fields=[ + ('event_trace_id', models.AutoField(serialize=False, primary_key=True)), + ('trace_session_id', models.IntegerField(default=0)), + ('event_data', models.TextField()), + ('message_id', models.IntegerField()), + ('client', models.ForeignKey(to='network_ui.Client')), + ], + ), + ] diff --git a/awx/network_ui/migrations/0028_coverage.py b/awx/network_ui/migrations/0028_coverage.py new file mode 100644 index 0000000000..f799f726c5 --- /dev/null +++ b/awx/network_ui/migrations/0028_coverage.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0027_eventtrace'), + ] + + operations = [ + migrations.CreateModel( + name='Coverage', + fields=[ + ('coverage_id', models.AutoField(serialize=False, primary_key=True)), + ('trace_session_id', models.IntegerField()), + ('coverage_data', models.TextField()), + ('client', models.ForeignKey(to='network_ui.Client')), + ], + ), + ] diff --git a/awx/network_ui/migrations/0029_topologysnapshot.py b/awx/network_ui/migrations/0029_topologysnapshot.py new file mode 100644 index 0000000000..e185927b32 --- /dev/null +++ b/awx/network_ui/migrations/0029_topologysnapshot.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-09 17:12 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0028_coverage'), + ] + + operations = [ + 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')), + ], + ), + ] diff --git a/awx/network_ui/migrations/0030_auto_20180110_1751.py b/awx/network_ui/migrations/0030_auto_20180110_1751.py new file mode 100644 index 0000000000..d21ca4d456 --- /dev/null +++ b/awx/network_ui/migrations/0030_auto_20180110_1751.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-10 17:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0029_topologysnapshot'), + ] + + 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(max_length=40)), + ], + ), + migrations.CreateModel( + name='Result', + fields=[ + ('result_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=20)), + ], + ), + migrations.CreateModel( + name='TestCase', + fields=[ + ('test_case_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(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()), + ('code_under_test', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.CodeUnderTest')), + ('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Result')), + ('test_case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.TestCase')), + ], + ), + migrations.RemoveField( + model_name='coverage', + name='client', + ), + migrations.RemoveField( + model_name='coverage', + name='trace_session_id', + ), + migrations.AddField( + model_name='coverage', + name='test_result', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='network_ui.TestResult'), + preserve_default=False, + ), + ] diff --git a/awx/network_ui/migrations/0031_auto_20180110_1752.py b/awx/network_ui/migrations/0031_auto_20180110_1752.py new file mode 100644 index 0000000000..c4474da956 --- /dev/null +++ b/awx/network_ui/migrations/0031_auto_20180110_1752.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-10 17:52 +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', 'Result') + for result in results: + Result.objects.get_or_create(name=result) + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0030_auto_20180110_1751'), + ] + + operations = [ + migrations.RunPython( + code=populate_result_types, + ), + ] diff --git a/awx/network_ui/migrations/0032_auto_20180112_2135.py b/awx/network_ui/migrations/0032_auto_20180112_2135.py new file mode 100644 index 0000000000..c43827ef58 --- /dev/null +++ b/awx/network_ui/migrations/0032_auto_20180112_2135.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-12 21:35 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0031_auto_20180110_1752'), + ] + + operations = [ + migrations.AddField( + model_name='testresult', + name='client', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client'), + preserve_default=False, + ), + migrations.AddField( + model_name='testresult', + name='id', + field=models.IntegerField(default=0), + ), + ] diff --git a/awx/network_ui/migrations/0033_auto_20180112_2202.py b/awx/network_ui/migrations/0033_auto_20180112_2202.py new file mode 100644 index 0000000000..c88ebe9cae --- /dev/null +++ b/awx/network_ui/migrations/0033_auto_20180112_2202.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-12 22:02 +from __future__ import unicode_literals + +from django.db import migrations + + +import yaml + +messages = yaml.load(''' +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: Undo, fields: [msg_type, sender, original_message]} + - {msg_type: Redo, fields: [msg_type, sender, original_message]} + - {msg_type: Deploy, fields: [msg_type, sender]} + - {msg_type: Destroy, fields: [msg_type, sender]} + - {msg_type: Discover, fields: [msg_type, sender]} + - {msg_type: Layout, fields: [msg_type, sender]} + - {msg_type: MultipleMessage, fields: [msg_type, sender, messages]} + - {msg_type: MouseEvent, fields: [msg_type, sender, x, y, type, trace_id]} + - {msg_type: MouseWheelEvent, fields: [msg_type, sender, delta, deltaX, deltaY, type, originalEvent, trace_id]} + - {msg_type: KeyEvent, fields: [msg_type, sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey, trace_id]} + - {msg_type: TouchEvent, fields: [msg_type, sender, type, touches]} + - {msg_type: StartRecording, fields: [msg_type, sender, trace_id]} + - {msg_type: StopRecording, fields: [msg_type, sender, trace_id]} + - {msg_type: ViewPort, fields: [msg_type, sender, scale, panX, panY, trace_id]} + - {msg_type: CopySite, fields: [msg_type, site]} + - {msg_type: GroupMove, fields: [msg_type, sender, id, x1, y1, x2, y2, previous_x1, previous_y1, previous_x2, previous_y2]} + - {msg_type: GroupCreate, fields: [msg_type, sender, id, x1, y1, x2, y2, name, type]} + - {msg_type: GroupDestroy, fields: [msg_type, sender, id, previous_x1, previous_y1, previous_x2, previous_y2, previous_name, previous_type]} + - {msg_type: GroupLabelEdit, fields: [msg_type, sender, id, name, previous_name]} + - {msg_type: GroupSelected, fields: [msg_type, sender, id]} + - {msg_type: GroupUnSelected, fields: [msg_type, sender, id]} + - {msg_type: GroupMembership, fields: [msg_type, sender, id, members]} + - {msg_type: TableCellEdit, fields: [msg_type, sender, sheet, col, row, old_value, new_value]} + - {msg_type: ProcessCreate, fields: [msg_type, id, name, type, device_id, x, y]} + - {msg_type: StreamCreate, fields: [msg_type, sender, id, from_id, to_id, label]} + - {msg_type: StreamDestroy, fields: [msg_type, sender, id, from_id, to_id, label]} + - {msg_type: StreamLabelEdit, fields: [msg_type, sender, id, label, previous_label]} + - {msg_type: StreamSelected, fields: [msg_type, sender, id]} + - {msg_type: StreamUnSelected, fields: [msg_type, sender, id]} + - {msg_type: FSMTrace, fields: [msg_type, order, sender, trace_id, fsm_name, from_state, to_state, recv_message_type]} + - {msg_type: ChannelTrace, fields: [msg_type, sender, trace_id, from_fsm, to_fsm, sent_message_type]} + - {msg_type: Snapshot, fields: [msg_type, sender, devices, links, groups, streams, order, trace_id]} + - {msg_type: EnableTest, fields: [msg_type]} + - {msg_type: DisableTest, fields: [msg_type]} + - {msg_type: StartTest, fields: [msg_type]} + - {msg_type: TestCompleted, fields: [msg_type]} + - {msg_type: TestResult, fields: [msg_type, sender, id, name, result, date, code_under_test]} + - {msg_type: Coverage, fields: [msg_type, sender, coverage, result_id]} +''') + + +def populate_message_types(apps, schema_editor): + + MessageType = apps.get_model('network_ui', 'MessageType') + for message in messages['messages']: + MessageType.objects.get_or_create(name=message['msg_type']) + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0032_auto_20180112_2135'), + ] + + operations = [ + migrations.RunPython( + code=populate_message_types, + ), + ] diff --git a/awx/network_ui/migrations/0034_auto_20180113_1725.py b/awx/network_ui/migrations/0034_auto_20180113_1725.py new file mode 100644 index 0000000000..8a062d468d --- /dev/null +++ b/awx/network_ui/migrations/0034_auto_20180113_1725.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-01-13 17:25 +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', 'TestCase') + TestCase.objects.get_or_create(name="Load", test_case_data=json.dumps(dict(runnable=False))) + + +class Migration(migrations.Migration): + + dependencies = [ + ('network_ui', '0033_auto_20180112_2202'), + ] + + operations = [ + migrations.RunPython( + code=add_load_test_case, + ), + ] diff --git a/awx/network_ui/models.py b/awx/network_ui/models.py index 7c3cee39dd..c18159cc97 100644 --- a/awx/network_ui/models.py +++ b/awx/network_ui/models.py @@ -175,3 +175,63 @@ 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,) + test_case_data = models.TextField() + + +class Result(models.Model): + + result_id = models.AutoField(primary_key=True,) + name = models.CharField(max_length=20,) + + +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,) + + +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/static/network_ui/designs/messages.yml b/awx/network_ui/static/network_ui/designs/messages.yml index 92813cb71e..c0d8c0fa7d 100644 --- a/awx/network_ui/static/network_ui/designs/messages.yml +++ b/awx/network_ui/static/network_ui/designs/messages.yml @@ -1,7 +1,7 @@ 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]} - - {msg_type: DeviceDestroy, fields: [msg_type, sender, id, previous_x, previous_y, previous_name, previous_type]} + - {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]} @@ -19,14 +19,13 @@ messages: - {msg_type: Discover, fields: [msg_type, sender]} - {msg_type: Layout, fields: [msg_type, sender]} - {msg_type: MultipleMessage, fields: [msg_type, sender, messages]} - - {msg_type: Coverage, fields: [msg_type, sender, coverage]} - - {msg_type: MouseEvent, fields: [msg_type, sender, x, y, type]} - - {msg_type: MouseWheelEvent, fields: [msg_type, sender, delta, deltaX, deltaY, type, originalEvent]} - - {msg_type: KeyEvent, fields: [msg_type, sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey]} + - {msg_type: MouseEvent, fields: [msg_type, sender, x, y, type, trace_id]} + - {msg_type: MouseWheelEvent, fields: [msg_type, sender, delta, deltaX, deltaY, type, originalEvent, trace_id]} + - {msg_type: KeyEvent, fields: [msg_type, sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey, trace_id]} - {msg_type: TouchEvent, fields: [msg_type, sender, type, touches]} - - {msg_type: StartRecording, fields: [msg_type, sender]} - - {msg_type: StopRecording, fields: [msg_type, sender]} - - {msg_type: ViewPort, fields: [msg_type, sender, scale, panX, panY]} + - {msg_type: StartRecording, fields: [msg_type, sender, trace_id]} + - {msg_type: StopRecording, fields: [msg_type, sender, trace_id]} + - {msg_type: ViewPort, fields: [msg_type, sender, scale, panX, panY, trace_id]} - {msg_type: CopySite, fields: [msg_type, site]} - {msg_type: GroupMove, fields: [msg_type, sender, id, x1, y1, x2, y2, previous_x1, previous_y1, previous_x2, previous_y2]} - {msg_type: GroupCreate, fields: [msg_type, sender, id, x1, y1, x2, y2, name, type]} @@ -44,4 +43,11 @@ messages: - {msg_type: StreamUnSelected, fields: [msg_type, sender, id]} - {msg_type: FSMTrace, fields: [msg_type, order, sender, trace_id, fsm_name, from_state, to_state, recv_message_type]} - {msg_type: ChannelTrace, fields: [msg_type, sender, trace_id, from_fsm, to_fsm, sent_message_type]} + - {msg_type: Snapshot, fields: [msg_type, sender, devices, links, groups, streams, order, trace_id]} + - {msg_type: EnableTest, fields: [msg_type]} + - {msg_type: DisableTest, fields: [msg_type]} + - {msg_type: StartTest, fields: [msg_type]} + - {msg_type: TestCompleted, fields: [msg_type]} + - {msg_type: TestResult, fields: [msg_type, sender, id, name, result, date, code_under_test]} + - {msg_type: Coverage, fields: [msg_type, sender, coverage, result_id]} diff --git a/awx/network_ui/static/network_ui/designs/test.yml b/awx/network_ui/static/network_ui/designs/test.yml new file mode 100644 index 0000000000..a224b16690 --- /dev/null +++ b/awx/network_ui/static/network_ui/designs/test.yml @@ -0,0 +1,49 @@ +diagram_id: 42 +name: diagram +states: +- id: 6 + label: Reporting + x: 926 + y: 721 +- id: 2 + label: Disabled + x: 895 + y: 344 +- id: 5 + label: Running + x: 720 + y: 922 +- id: 1 + label: Ready + x: 722 + y: 509 +- id: 3 + label: Start + x: 702 + y: 186 +- id: 4 + label: Loading + x: 524 + y: 710 +transitions: +- from_state: Running + label: onTestCompleted + to_state: Reporting +- from_state: Ready + label: onDisable + to_state: Disabled +- from_state: Start + label: start + to_state: Disabled +- from_state: Loading + label: onTestLoaded + to_state: Running +- from_state: Disabled + label: onEnable + to_state: Ready +- from_state: Reporting + label: onTestReported + to_state: Ready +- from_state: Ready + label: onStartTest + to_state: Loading diff --git a/awx/network_ui/static/network_ui/test.js b/awx/network_ui/static/network_ui/test.js new file mode 100644 index 0000000000..3f850386d7 --- /dev/null +++ b/awx/network_ui/static/network_ui/test.js @@ -0,0 +1,112 @@ +var inherits = require('inherits'); +var fsm = require('./fsm.js'); + +function _State () { +} +inherits(_State, fsm._State); + + +function _Disabled () { + this.name = 'Disabled'; +} +inherits(_Disabled, _State); +var Disabled = new _Disabled(); +exports.Disabled = Disabled; + +function _Start () { + this.name = 'Start'; +} +inherits(_Start, _State); +var Start = new _Start(); +exports.Start = Start; + +function _Running () { + this.name = 'Running'; +} +inherits(_Running, _State); +var Running = new _Running(); +exports.Running = Running; + +function _Loading () { + this.name = 'Loading'; +} +inherits(_Loading, _State); +var Loading = new _Loading(); +exports.Loading = Loading; + +function _Ready () { + this.name = 'Ready'; +} +inherits(_Ready, _State); +var Ready = new _Ready(); +exports.Ready = Ready; + +function _Reporting () { + this.name = 'Reporting'; +} +inherits(_Reporting, _State); +var Reporting = new _Reporting(); +exports.Reporting = Reporting; + + + + +_Disabled.prototype.onEnable = function (controller) { + + controller.changeState(Ready); + +}; +_Disabled.prototype.onEnable.transitions = ['Ready']; + + + +_Start.prototype.start = function (controller) { + + controller.changeState(Disabled); + +}; +_Start.prototype.start.transitions = ['Disabled']; + + + +_Running.prototype.onTestCompleted = function (controller) { + + controller.changeState(Reporting); + +}; +_Running.prototype.onTestCompleted.transitions = ['Reporting']; + + + +_Loading.prototype.onTestLoaded = function (controller) { + + controller.changeState(Running); + +}; +_Loading.prototype.onTestLoaded.transitions = ['Running']; + + + +_Ready.prototype.onDisable = function (controller) { + + controller.changeState(Disabled); + +}; +_Ready.prototype.onDisable.transitions = ['Disabled']; + +_Ready.prototype.onStartTest = function (controller) { + + controller.changeState(Loading); + +}; +_Ready.prototype.onStartTest.transitions = ['Loading']; + + + +_Reporting.prototype.onTestReported = function (controller) { + + controller.changeState(Ready); + +}; +_Reporting.prototype.onTestReported.transitions = ['Ready']; + diff --git a/awx/network_ui/templates/network_ui/index.html b/awx/network_ui/templates/network_ui/index.html deleted file mode 100644 index fbee2cdc9b..0000000000 --- a/awx/network_ui/templates/network_ui/index.html +++ /dev/null @@ -1,11 +0,0 @@ -