Adds network UI test framework

This adds a test framework to drive UI tests from the client
instead of injecting events from the websocket.  Tests consist
of a pair of snapshots (before and after the test) and
a list of UI events to process.  Tests are run using a FSM
in the client that controls the resetting of state to the snapshot,
injecting the events into the UI, recording test coverage,
and reporting tests to the server.

* Adds design for event trace table
* Adds design for a coverage tracking table
* Adds models for EventTrace and Coverage
* Adds trace_id to recording messages
* Adds design for TopologySnapshot table
* Adds order to TopologySnapshot table
* Adds TopologySnapshot table
* Adds Snapshot message when recordings are started and stoppped
* Adds models for tracking test cases and test results
* Adds designs for a test runner FSM
* Updates test management commands with new schema
* Adds download recording button
* Adds models to track tests
* Adds ui test runner
* Adds id and client to TestResult design
* Adds id and client to TestResult
* Update message types
* Stores test results and code coverage from the test runner
* Adds tool to generate a test coverage report
* Adds APIs for tests and code coverage
* Adds per-test-case coverage reports
* Breaks out coverage for loading the modules from the tests
* Re-raises server-side errors
* Captures errors during tests
* Adds defaults for host name and host type
* Disables test FSM trace storage
* Adds support for sending server error message to the client
* Resets the UI flags, history, and toolbox contents between tests
* Adds istanbul instrumentation to network-ui
This commit is contained in:
Ben Thomasson 2018-01-08 11:34:23 -05:00
parent eeaf7c257c
commit bf7f4ee1e1
No known key found for this signature in database
GPG Key ID: 5818EF4CC895D5F5
38 changed files with 1534 additions and 97 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,5 @@
def natural_numbers(i=1):
while True:
yield i
i += 1

View File

@ -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')),
],
),
]

View File

@ -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')),
],
),
]

View File

@ -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')),
],
),
]

View File

@ -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,
),
]

View File

@ -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,
),
]

View File

@ -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),
),
]

View File

@ -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,
),
]

View File

@ -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,
),
]

View File

@ -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',)

View File

@ -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]}

View File

@ -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

View File

@ -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'];

View File

@ -1,11 +0,0 @@
<ul>
<li><a href="/#/topology/">New</a></li>
{%for o in topologies%}
<li>
<a href="/#/topology?topology_id={{o.pk}}">{{o.pk}} {{o}}</a>
{%for device in o.device_set.all%}
{{device}}
{%endfor%}
</li>
{%endfor%}
</ul>

View File

@ -0,0 +1,5 @@
<form action="/network_ui/upload_test" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form}}
<button type="submit">Upload Test</button>
</form>

View File

@ -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}

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Usage:
coverage_report [options] <server>
Options:
-h, --help Show this page
--debug Show debug logging
--verbose Show verbose logging
"""
from docopt import docopt
import logging
import sys
import os
import requests
import subprocess
logger = logging.getLogger('coverage_report')
TESTS_API = '/network_ui/tests'
def main(args=None):
if args is None:
args = sys.argv[1:]
parsed_args = docopt(__doc__, args)
if parsed_args['--debug']:
logging.basicConfig(level=logging.DEBUG)
elif parsed_args['--verbose']:
logging.basicConfig(level=logging.INFO)
else:
logging.basicConfig(level=logging.WARNING)
print (parsed_args['<server>'])
server = parsed_args['<server>']
tests = requests.get(server + TESTS_API, verify=False).json()
for test in tests['tests']:
if not os.path.exists(test['name']):
os.mkdir(test['name'])
with open(test['name'] + "/coverage.json", 'w') as f:
f.write(requests.get(server + test['coverage'], verify=False).text)
for test in tests['tests']:
subprocess.Popen('istanbul report html', shell=True, cwd=test['name']).wait()
subprocess.Popen('istanbul report html', shell=True).wait()
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv[1:]))

View File

@ -0,0 +1,2 @@
requests
docopt

View File

@ -7,7 +7,11 @@ import awx.network_ui.routing
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<pk>[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'),
url(r'^$', views.index, name='index'),

View File

@ -1,12 +1,16 @@
# Copyright (c) 2017 Red Hat, Inc
from django.shortcuts import render
from django import forms
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist
import yaml
import json
# Create your views here.
from .models import Topology, FSMTrace
from .models import Topology, FSMTrace, EventTrace, TopologySnapshot
from .models import TestCase, TestResult, Coverage
from .serializers import topology_data
@ -56,3 +60,79 @@ def download_trace(request):
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

View File

@ -58,6 +58,17 @@ const base = {
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'istanbul-instrumenter-loader',
options: { esModules: true }
},
enforce: 'pre',
include: [
/src\/network-ui\//
]
},
{
test: /\.js$/,
loader: 'babel-loader',

View File

@ -176,23 +176,18 @@ function MultipleMessage(sender, messages) {
}
exports.MultipleMessage = MultipleMessage;
function Coverage(sender, coverage) {
this.msg_type = "Coverage";
this.sender = sender;
this.coverage = coverage;
}
exports.Coverage = Coverage;
function MouseEvent(sender, x, y, type) {
function MouseEvent(sender, x, y, type, trace_id) {
this.msg_type = "MouseEvent";
this.sender = sender;
this.x = x;
this.y = y;
this.type = type;
this.trace_id = trace_id;
}
exports.MouseEvent = MouseEvent;
function MouseWheelEvent(sender, delta, deltaX, deltaY, type, metaKey) {
function MouseWheelEvent(sender, delta, deltaX, deltaY, type, metaKey, trace_id) {
this.msg_type = "MouseWheelEvent";
this.sender = sender;
this.delta = delta;
@ -200,10 +195,11 @@ function MouseWheelEvent(sender, delta, deltaX, deltaY, type, metaKey) {
this.deltaY = deltaY;
this.type = type;
this.originalEvent = {metaKey: metaKey};
this.trace_id = trace_id;
}
exports.MouseWheelEvent = MouseWheelEvent;
function KeyEvent(sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey) {
function KeyEvent(sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey, trace_id) {
this.msg_type = "KeyEvent";
this.sender = sender;
this.key = key;
@ -213,6 +209,7 @@ function KeyEvent(sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey
this.shiftKey = shiftKey;
this.ctrlKey = ctrlKey;
this.metaKey = metaKey;
this.trace_id = trace_id;
}
exports.KeyEvent = KeyEvent;
@ -224,24 +221,27 @@ function TouchEvent(sender, type, touches) {
}
exports.TouchEvent = TouchEvent;
function StartRecording(sender) {
function StartRecording(sender, trace_id) {
this.msg_type = "StartRecording";
this.sender = sender;
this.trace_id = trace_id;
}
exports.StartRecording = StartRecording;
function StopRecording(sender) {
function StopRecording(sender, trace_id) {
this.msg_type = "StopRecording";
this.sender = sender;
this.trace_id = trace_id;
}
exports.StopRecording = StopRecording;
function ViewPort(sender, scale, panX, panY) {
function ViewPort(sender, scale, panX, panY, trace_id) {
this.msg_type = "ViewPort";
this.sender = sender;
this.scale = scale;
this.panX = panX;
this.panY = panY;
this.trace_id = trace_id;
}
exports.ViewPort = ViewPort;
@ -446,3 +446,54 @@ function ChannelTrace(from_fsm, to_fsm, sent_message_type) {
this.sent_message_type = sent_message_type;
}
exports.ChannelTrace = ChannelTrace;
function Snapshot(sender, devices, links, groups, streams, order, trace_id) {
this.msg_type = 'Snapshot';
this.sender = 0;
this.devices = devices;
this.links = links;
this.groups = groups;
this.streams = streams;
this.order = order;
this.trace_id = trace_id;
}
exports.Snapshot = Snapshot;
function EnableTest() {
this.msg_type = "EnableTest";
}
exports.EnableTest = EnableTest;
function DisableTest() {
this.msg_type = "DisableTest";
}
exports.DisableTest = DisableTest;
function StartTest() {
this.msg_type = "StartTest";
}
exports.StartTest = StartTest;
function TestCompleted() {
this.msg_type = "TestCompleted";
}
exports.TestCompleted = TestCompleted;
function TestResult(sender, id, name, result, date, code_under_test) {
this.msg_type = "TestResult";
this.sender = sender;
this.id = id;
this.name = name;
this.result = result;
this.date = date;
this.code_under_test = code_under_test;
}
exports.TestResult = TestResult;
function Coverage(sender, coverage, result_id) {
this.msg_type = "Coverage";
this.sender = sender;
this.coverage = coverage;
this.result_id = result_id;
}
exports.Coverage = Coverage;

View File

@ -36,8 +36,12 @@ Device.prototype.toJSON = function () {
x: this.x,
y: this.y,
type: this.type,
interfaces: this.interfaces,
processes: this.processes};
interfaces: this.interfaces.map(function (x) {
return x.toJSON();
}),
processes: this.processes.map(function (x) {
return x.toJSON();
})};
};
Device.prototype.is_selected = function (x, y) {
@ -726,6 +730,11 @@ function Process(id, name, type, x, y) {
}
exports.Process = Process;
Process.prototype.toJSON = function () {
return {id: this.id,
name: this.name};
};
function Stream(id, from_device, to_device, label) {
this.id = id;
this.from_device = from_device;
@ -919,3 +928,21 @@ Stream.prototype.start_arc_angle_rad = function () {
Stream.prototype.start_arc_angle = function () {
return this.start_arc_angle_rad() * 180 / Math.PI;
};
function Test(name, event_trace, fsm_trace, pre_test_snapshot, post_test_snapshot) {
this.name = name;
this.event_trace = event_trace;
this.fsm_trace = fsm_trace;
this.pre_test_snapshot = pre_test_snapshot;
this.post_test_snapshot = post_test_snapshot;
}
exports.Test = Test;
function TestResult(id, name, result, date, code_under_test) {
this.id = id;
this.name = name;
this.result = result;
this.date = date;
this.code_under_test = code_under_test;
}
exports.TestResult = TestResult;

View File

@ -33,6 +33,7 @@ var inventoryToolboxClipPath = require('./inventory.toolbox.clip.path.directive.
var statusLight = require('./status.light.directive.js');
var taskStatus = require('./task.status.directive.js');
var debug = require('./debug.directive.js');
var test_results = require('./test_results.directive.js');
var awxNetworkUI = require('./network.ui.directive.js');
var networkUI = angular.module('networkUI', [
@ -70,6 +71,7 @@ var networkUI = angular.module('networkUI', [
.directive('awxNetInventoryToolboxClipPath', inventoryToolboxClipPath.inventoryToolboxClipPath)
.directive('awxNetStatusLight', statusLight.statusLight)
.directive('awxNetTaskStatus', taskStatus.taskStatus)
.directive('awxNetTestResults', test_results.test_results)
.directive('awxNetworkUi', awxNetworkUI.awxNetworkUI);
exports.networkUI = networkUI;

View File

@ -15,6 +15,7 @@ var stream_fsm = require('./stream.fsm.js');
var group = require('./group.js');
var buttons = require('./buttons.js');
var time = require('./time.js');
var test_fsm = require('./test.fsm.js');
var util = require('./util.js');
var models = require('./models.js');
var messages = require('./messages.js');
@ -22,7 +23,7 @@ var svg_crowbar = require('./svg-crowbar.js');
var ReconnectingWebSocket = require('reconnectingwebsocket');
var NetworkUIController = function($scope, $document, $location, $window, $http,
$q, $state, ProcessErrors) {
$q, $state, ProcessErrors, ConfigService) {
window.scope = $scope;
var i = 0;
@ -96,6 +97,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.overall_toolbox_collapsed = false;
$scope.time_pointer = -1;
$scope.frame = 0;
@ -108,6 +110,14 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.groups = [];
$scope.processes = [];
$scope.configurations = [];
$scope.tests = [];
$scope.current_tests = [];
$scope.current_test = null;
$scope.testing = false;
$scope.version = null;
$scope.test_events = [];
$scope.test_results = [];
$scope.test_errors = [];
$scope.streams = [];
$scope.view_port = {'x': 0,
'y': 0,
@ -118,7 +128,9 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.trace_id = $scope.trace_id_seq();
$scope.send_trace_message = function (message) {
console.log(message);
if (!$scope.recording) {
return;
}
message.sender = $scope.client_id;
message.trace_id = $scope.trace_id;
message.message_id = $scope.message_id_seq();
@ -148,6 +160,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.site_controller = new fsm.FSMController($scope, "site_fsm", site_fsm.Disable, $scope);
$scope.buttons_controller = new fsm.FSMController($scope, "buttons_fsm", buttons.Start, $scope);
$scope.time_controller = new fsm.FSMController($scope, "time_fsm", time.Start, $scope);
$scope.test_controller = new fsm.FSMController($scope, "test_fsm", test_fsm.Start, $scope);
$scope.app_toolbox_controller = new fsm.FSMController($scope, "toolbox_fsm", toolbox_fsm.Start, $scope);
//App Toolbox Setup
@ -185,6 +198,12 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
let host = hosts[i];
console.log(host);
host.data = jsyaml.safeLoad(host.variables);
if (host.data.type == undefined) {
host.data.type = 'unknown';
}
if (host.data.name == undefined) {
host.data.name = host.name;
}
var device = new models.Device(0, host.data.name, 0, 0, host.data.type, host.id, host.variables);
device.icon = true;
$scope.inventory_toolbox.items.push(device);
@ -288,9 +307,13 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.mode_controller.delegate_channel = new fsm.Channel($scope.mode_controller,
$scope.time_controller,
$scope);
$scope.test_controller.delegate_channel = new fsm.Channel($scope.test_controller,
$scope.mode_controller,
$scope);
$scope.first_channel = new fsm.Channel(null,
$scope.mode_controller,
$scope.test_controller,
$scope);
var getMouseEventResult = function (mouseEvent) {
@ -464,7 +487,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseDown', $event);
@ -475,7 +498,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseUp', $event);
@ -486,7 +509,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseLeaveResult = getMouseEventResult($event);
$scope.cursor.hidden = true;
@ -496,7 +519,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
//var coords = getCrossBrowserElementCoords($event);
$scope.cursor.hidden = false;
@ -513,7 +536,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$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.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseOverResult = getMouseEventResult($event);
$scope.cursor.hidden = false;
@ -529,11 +552,11 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
var deltaY = $event.deltaY;
// console.log([$event, delta, deltaX, deltaY]);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey));
$scope.send_control_message(new messages.MouseWheelEvent($scope.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]);
event.preventDefault();
$event.preventDefault();
};
$scope.onKeyDown = function ($event) {
@ -545,7 +568,8 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$event.altKey,
$event.shiftKey,
$event.ctrlKey,
$event.metaKey));
$event.metaKey,
$scope.trace_id));
}
$scope.last_event = $event;
$scope.last_key = $event.key;
@ -653,6 +677,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
};
$scope.onRenameContextButton = function (button) {
console.log(button.name);
$scope.context_menus[0].enabled = false;
$scope.first_channel.send("LabelEdit", {});
};
@ -690,14 +715,31 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
console.log(button.name);
$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),
[new messages.StartRecording($scope.client_id, $scope.trace_id),
new messages.ViewPort($scope.client_id,
$scope.current_scale,
$scope.panX,
$scope.panY)]));
$scope.panY,
$scope.trace_id),
new messages.Snapshot($scope.client_id,
$scope.devices,
$scope.links,
$scope.groups,
$scope.streams,
0,
$scope.trace_id)]));
} else {
$scope.send_control_message(new messages.StopRecording($scope.client_id));
$scope.send_control_message(new messages.MultipleMessage($scope.client_id,
[new messages.Snapshot($scope.client_id,
$scope.devices,
$scope.links,
$scope.groups,
$scope.streams,
1,
$scope.trace_id),
new messages.StopRecording($scope.client_id, $scope.trace_id)]));
}
};
@ -789,6 +831,25 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
console.log(button.label);
window.open("/network_ui/download_trace?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.client_id);
};
$scope.onDownloadRecordingButton = function (button) {
console.log(button.label);
window.open("/network_ui/download_recording?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.client_id);
};
$scope.onUploadTestButton = function (button) {
console.log(button.name);
window.open("/network_ui/upload_test", "_top");
};
$scope.onRunTestsButton = function (button) {
console.log(button.name);
$scope.test_results = [];
$scope.current_tests = $scope.tests.slice();
$scope.first_channel.send("EnableTest", new messages.EnableTest());
};
// Buttons
var button_offset = 200;
@ -802,6 +863,9 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
new models.Button("CONFIGURE", button_offset + 520, 48, 90, 30, $scope.onConfigureButton, $scope),
new models.Button("EXPORT YAML", button_offset + 620, 48, 120, 30, $scope.onExportYamlButton, $scope),
new models.Button("DOWNLOAD TRACE", button_offset + 750, 48, 150, 30, $scope.onDownloadTraceButton, $scope),
new models.Button("DOWNLOAD RECORDING", button_offset + 910, 48, 170, 30, $scope.onDownloadRecordingButton, $scope),
new models.Button("UPLOAD TEST", button_offset + 10, 88, 100, 30, $scope.onUploadTestButton, $scope),
new models.Button("RUN TESTS", button_offset + 120, 88, 100, 30, $scope.onRunTestsButton, $scope),
];
var LAYERS_X = 160;
@ -1429,6 +1493,8 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
//Erase the existing state
$scope.devices = [];
$scope.links = [];
$scope.groups = [];
$scope.streams = [];
var device_map = {};
var device_interface_map = {};
@ -1634,12 +1700,6 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
}
};
$scope.send_coverage = function () {
console.log("Sending coverage");
if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) {
$scope.send_control_message(new messages.Coverage($scope.client_id, window.__coverage__));
}
};
$scope.control_socket.onmessage = function(message) {
@ -1743,6 +1803,112 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
map.set(key, stream.offset + 1);
}
};
setInterval( function () {
var test_event = null;
if ($scope.test_events.length > 0) {
test_event = $scope.test_events.shift();
console.log(test_event);
test_event.sender = 0;
try {
$scope.first_channel.send(test_event.msg_type, test_event);
} catch (err) {
$scope.test_errors.push(err);
}
}
$scope.$apply();
}, 10);
ConfigService
.getConfig()
.then(function(config){
$scope.version = config.version;
});
$scope.reset_coverage = function() {
var i = null;
var coverage = null;
var f = null;
if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) {
for (f in window.__coverage__) {
coverage = window.__coverage__[f];
for (i in coverage.b) {
coverage.b[i] = [0, 0];
}
for (i in coverage.f) {
coverage.f[i] = 0;
}
for (i in coverage.s) {
coverage.s[i] = 0;
}
}
}
};
$scope.reset_flags = function () {
$scope.debug = {'hidden': true};
$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.view_controller.state = view.Start;
$scope.view_controller.state.start($scope.view_controller);
$scope.device_detail_controller.state = device_detail_fsm.Start;
$scope.device_detail_controller.state.start($scope.device_detail_controller);
$scope.move_controller.state = move.Start;
$scope.move_controller.state.start($scope.move_controller);
$scope.link_controller.state = link.Start;
$scope.link_controller.state.start($scope.link_controller);
$scope.stream_controller.state = stream_fsm.Start;
$scope.stream_controller.state.start($scope.stream_controller);
$scope.group_controller.state = group.Start;
$scope.group_controller.state.start($scope.group_controller);
$scope.rack_controller.state = rack_fsm.Disable;
$scope.rack_controller.state.start($scope.rack_controller);
$scope.site_controller.state = site_fsm.Disable;
$scope.site_controller.state.start($scope.site_controller);
$scope.buttons_controller.state = buttons.Start;
$scope.buttons_controller.state.start($scope.buttons_controller);
$scope.time_controller.state = time.Start;
$scope.time_controller.state.start($scope.time_controller);
$scope.app_toolbox_controller.state = toolbox_fsm.Start;
$scope.app_toolbox_controller.state.start($scope.app_toolbox_controller);
$scope.inventory_toolbox_controller.state = toolbox_fsm.Start;
$scope.inventory_toolbox_controller.state.start($scope.inventory_toolbox_controller);
$scope.rack_toolbox_controller.state = toolbox_fsm.Start;
$scope.rack_toolbox_controller.state.start($scope.rack_toolbox_controller);
$scope.site_toolbox_controller.state = toolbox_fsm.Start;
$scope.site_toolbox_controller.state.start($scope.site_toolbox_controller);
$scope.mode_controller.state = mode_fsm.Start;
$scope.mode_controller.state.start($scope.mode_controller);
};
$scope.reset_history = function () {
$scope.history = [];
};
$scope.reset_toolboxes = function () {
$scope.app_toolbox.items = [];
$scope.app_toolbox.items.push(new models.Process(0, 'BGP', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'OSPF', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'STP', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'Zero Pipeline', 'process', 0, 0));
for(i = 0; i < $scope.app_toolbox.items.length; i++) {
$scope.app_toolbox.items[i].icon = true;
}
$scope.inventory_toolbox.items = [];
$scope.rack_toolbox.items = [];
$scope.site_toolbox.items = [];
};
};
exports.NetworkUIController = NetworkUIController;

View File

@ -116,6 +116,7 @@
ng-attr-transform="translate({{context_menus[0].x}}, {{context_menus[0].y}})">
</g>
<g awx-net-debug></g>
<g awx-net-test-results></g>
<g awx-net-cursor></g>
<g ng-repeat="touch in touches">
<g awx-net-touch></g>

View File

@ -766,3 +766,8 @@
.NetworkUI__contextMenuButton-pressed{
fill:@button-body-hover;
}
.NetworkUI__test_results {
fill: @light-background;
stroke: @dark-widget-detail;
}

View File

@ -0,0 +1,160 @@
var inherits = require('inherits');
var fsm = require('./fsm.js');
var messages = require('./messages.js');
var models = require('./models.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.onEnableTest = function (controller) {
controller.changeState(Ready);
};
_Disabled.prototype.onEnableTest.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'];
_Reporting.prototype.start = function (controller) {
var test_result = null;
controller.scope.replay = false;
controller.scope.disconnected = false;
controller.scope.recording = false;
var result = "passed";
if (controller.scope.test_errors.length > 0) {
result = "errored";
}
test_result = new models.TestResult(controller.scope.test_result_id_seq(),
controller.scope.current_test.name,
result,
new Date().toISOString(),
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));
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.changeState(Loading);
};
_Reporting.prototype.start.transitions = ['Loading'];
_Loading.prototype.start = function (controller) {
if (controller.scope.current_tests.length === 0) {
controller.changeState(Disabled);
} else {
console.log("Starting test");
controller.scope.current_test = controller.scope.current_tests.shift();
controller.scope.onSnapshot(controller.scope.current_test.pre_test_snapshot);
controller.scope.replay = true;
controller.scope.disconnected = true;
controller.scope.test_errors = [];
controller.scope.test_events = controller.scope.current_test.event_trace.slice();
controller.scope.test_events.push(new messages.TestCompleted());
controller.scope.reset_coverage();
controller.scope.reset_flags();
controller.scope.reset_fsm_state();
controller.scope.reset_history();
controller.scope.reset_toolboxes();
controller.changeState(Running);
}
};
_Loading.prototype.start.transitions = ['Running'];
_Ready.prototype.onDisableTest = function (controller) {
controller.changeState(Disabled);
};
_Ready.prototype.onDisableTest.transitions = ['Disabled'];
_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));
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.changeState(Loading);
};
_Ready.prototype.start.transitions = ['Loading'];

View File

@ -0,0 +1,8 @@
/* Copyright (c) 2018 Red Hat, Inc. */
const templateUrl = require('~network-ui/test_results.partial.svg');
function test_results () {
return { restrict: 'A', templateUrl};
}
exports.test_results = test_results;

View File

@ -0,0 +1,14 @@
<g ng-if="test_results.length > 0">
<g ng-attr-transform="translate({{graph.width - 300}}, {{graph.height-500}})">
<rect class="NetworkUI__test_results" width="280" height="480" x="0" y="0">
</rect>
<g transform="translate(20, 20)">
<text class="NetworkUI__text">Test Results {{version}}</text>
<g ng-repeat="result in test_results track by $index">
<g ng-attr-transform="translate(0, {{$index * 20 + 20}})">
<text class="NetworkUI__text">{{result.name}} - {{result.result}}</text>
</g>
<g>
</g>
</g>
</g>

View File

@ -3,6 +3,7 @@ var inherits = require('inherits');
var fsm = require('./fsm.js');
var messages = require('./messages.js');
var util = require('./util.js');
var models = require('./models.js');
function _State () {
}
@ -539,3 +540,23 @@ _Present.prototype.undo = function(controller) {
controller.changeState(Past);
}
};
_Present.prototype.onTestCase = function(controller, msg_type, message) {
console.log([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.onError = function(controller, msg_type, message) {
console.log(["onError", msg_type, message]);
throw new Error("ServerError: " + message);
};

View File

@ -2,7 +2,7 @@
var angular = require('angular');
var tower = angular.module('tower', ['tablesUI', 'networkUI', 'ui.router']);
var tower = angular.module('tower', ['networkUI', 'ui.router']);
tower.config(function($stateProvider, $urlRouterProvider) {

View File

@ -61,6 +61,7 @@
"html-loader": "^0.5.1",
"html-webpack-harddisk-plugin": "^0.1.0",
"html-webpack-plugin": "^2.30.1",
"istanbul-instrumenter-loader": "^3.0.0",
"jasmine-core": "^2.5.2",
"jshint": "^2.9.4",
"jshint-stylish": "^2.2.0",