Simon Hunt

GUI -- TopoView - Migrated helper functions to topoModel.js.

- moved randomized functions to random.js (so we can mock them).

Change-Id: Ic56ce64c036d36f34798f0df9f03a7d09335a2ab
/*
* Copyright 2014,2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Random -- Encapsulated randomness
*/
(function () {
'use strict';
var $log, fs;
var halfRoot2 = 0.7071;
// given some value, s, returns an integer between -s/2 and s/2
// e.g. s = 100; result in the range [-50..50)
function spread(s) {
return Math.floor((Math.random() * s) - s / 2);
}
// for a given dimension, d, choose a random value somewhere between
// 0 and d where the value is within (d / (2 * sqrt(2))) of d/2.
function randDim(d) {
return d / 2 + spread(d * halfRoot2);
}
angular.module('onosUtil')
.factory('RandomService', ['$log', 'FnService',
function (_$log_, _fs_) {
$log = _$log_;
fs = _fs_;
return {
spread: spread,
randDim: randDim
};
}]);
}());
......@@ -35,6 +35,7 @@
<script src="fw/util/util.js"></script>
<script src="fw/util/fn.js"></script>
<script src="fw/util/random.js"></script>
<script src="fw/util/theme.js"></script>
<script src="fw/util/keys.js"></script>
......@@ -81,6 +82,7 @@
<script src="view/topo/topo.js"></script>
<script src="view/topo/topoEvent.js"></script>
<script src="view/topo/topoForce.js"></script>
<script src="view/topo/topoModel.js"></script>
<script src="view/topo/topoPanel.js"></script>
<script src="view/topo/topoInst.js"></script>
<script src="view/device/device.js"></script>
......
......@@ -130,8 +130,8 @@
// callback invoked when the SVG view has been resized..
function svgResized(dim) {
tfs.resize(dim);
function svgResized(s) {
tfs.newDim([s.width, s.height]);
}
// --- Background Map ------------------------------------------------
......@@ -203,6 +203,7 @@
_ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) {
var self = this,
projection,
dim,
uplink = {
// provides function calls back into this space
showNoDevs: showNoDevs,
......@@ -230,6 +231,7 @@
tes.closeSock();
tps.destroyPanels();
tis.destroyInst();
tfs.destroyForce();
});
// svg layer and initialization of components
......@@ -237,6 +239,7 @@
svg = ovtopo.select('svg');
// set the svg size to match that of the window, less the masthead
svg.attr(fs.windowSize(mast.mastHeight()));
dim = [svg.attr('width'), svg.attr('height')];
setUpKeys();
setUpDefs();
......@@ -250,7 +253,7 @@
);
forceG = zoomLayer.append('g').attr('id', 'topo-force');
tfs.initForce(forceG, uplink, svg.attr('width'), svg.attr('height'));
tfs.initForce(forceG, uplink, dim);
tis.initInst();
tps.initPanels();
tes.openSock();
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Topology Model Module.
Auxiliary functions for the model of the topology; that is, our internal
representations of devices, hosts, links, etc.
*/
(function () {
'use strict';
// injected refs
var $log, fs, rnd, api;
var dim; // dimensions of layout, as [w,h]
// configuration 'constants'
var defaultLinkType = 'direct',
nearDist = 15;
function coordFromLngLat(loc) {
var p = api.projection();
return p ? p([loc.lng, loc.lat]) : [0, 0];
}
function lngLatFromCoord(coord) {
var p = api.projection();
return p ? p.invert(coord) : [0, 0];
}
function positionNode(node, forUpdate) {
var meta = node.metaUi,
x = meta && meta.x,
y = meta && meta.y,
xy;
// If we have [x,y] already, use that...
if (x && y) {
node.fixed = true;
node.px = node.x = x;
node.py = node.y = y;
return;
}
var location = node.location,
coord;
if (location && location.type === 'latlng') {
coord = coordFromLngLat(location);
node.fixed = true;
node.px = node.x = coord[0];
node.py = node.y = coord[1];
return true;
}
// if this is a node update (not a node add).. skip randomizer
if (forUpdate) {
return;
}
// Note: Placing incoming unpinned nodes at exactly the same point
// (center of the view) causes them to explode outwards when
// the force layout kicks in. So, we spread them out a bit
// initially, to provide a more serene layout convergence.
// Additionally, if the node is a host, we place it near
// the device it is connected to.
function rand() {
return {
x: rnd.randDim(dim[0]),
y: rnd.randDim(dim[1])
};
}
function near(node) {
return {
x: node.x + nearDist + rnd.spread(nearDist),
y: node.y + nearDist + rnd.spread(nearDist)
};
}
function getDevice(cp) {
var d = api.lookup[cp.device];
return d || rand();
}
xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
angular.extend(node, xy);
}
function mkSvgCls(dh, t, on) {
var ndh = 'node ' + dh,
ndht = t ? ndh + ' ' + t : ndh;
return on ? ndht + ' online' : ndht;
}
function createDeviceNode(device) {
var node = device;
// Augment as needed...
node.class = 'device';
node.svgClass = mkSvgCls('device', device.type, device.online);
positionNode(node);
return node;
}
function createHostNode(host) {
var node = host;
// Augment as needed...
node.class = 'host';
if (!node.type) {
node.type = 'endstation';
}
node.svgClass = mkSvgCls('host', node.type);
positionNode(node);
return node;
}
function createHostLink(host) {
var src = host.id,
dst = host.cp.device,
id = host.ingress,
lnk = linkEndPoints(src, dst);
if (!lnk) {
return null;
}
// Synthesize link ...
angular.extend(lnk, {
key: id,
class: 'link',
type: function () { return 'hostLink'; },
online: function () {
// hostlink target is edge switch
return lnk.target.online;
},
linkWidth: function () { return 1; }
});
return lnk;
}
function createLink(link) {
var lnk = linkEndPoints(link.src, link.dst);
if (!lnk) {
return null;
}
angular.extend(lnk, {
key: link.id,
class: 'link',
fromSource: link,
// functions to aggregate dual link state
type: function () {
var s = lnk.fromSource,
t = lnk.fromTarget;
return (s && s.type) || (t && t.type) || defaultLinkType;
},
online: function () {
var s = lnk.fromSource,
t = lnk.fromTarget,
both = lnk.source.online && lnk.target.online;
return both && ((s && s.online) || (t && t.online));
},
linkWidth: function () {
var s = lnk.fromSource,
t = lnk.fromTarget,
ws = (s && s.linkWidth) || 0,
wt = (t && t.linkWidth) || 0;
return Math.max(ws, wt);
}
});
return lnk;
}
function linkEndPoints(srcId, dstId) {
var srcNode = api.lookup[srcId],
dstNode = api.lookup[dstId],
sMiss = !srcNode ? missMsg('src', srcId) : '',
dMiss = !dstNode ? missMsg('dst', dstId) : '';
if (sMiss || dMiss) {
$log.error('Node(s) not on map for link:' + sMiss + dMiss);
//logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
return null;
}
return {
source: srcNode,
target: dstNode,
x1: srcNode.x,
y1: srcNode.y,
x2: dstNode.x,
y2: dstNode.y
};
}
function missMsg(what, id) {
return '\n[' + what + '] "' + id + '" missing';
}
// ==========================
// Module definition
angular.module('ovTopo')
.factory('TopoModelService',
['$log', 'FnService', 'RandomService',
function (_$log_, _fs_, _rnd_) {
$log = _$log_;
fs = _fs_;
rnd = _rnd_;
function initModel(_api_, _dim_) {
api = _api_;
dim = _dim_;
}
function newDim(_dim_) {
dim = _dim_;
}
return {
initModel: initModel,
newDim: newDim,
positionNode: positionNode,
createDeviceNode: createDeviceNode,
createHostNode: createHostNode,
createHostLink: createHostLink,
createLink: createLink,
coordFromLngLat: coordFromLngLat,
lngLatFromCoord: lngLatFromCoord,
}
}]);
}());
/*
* Copyright 2014,2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Util -- Random Service - Unit Tests
*/
describe('factory: fw/util/random.js', function() {
var rnd, $log, fs;
beforeEach(module('onosUtil'));
beforeEach(inject(function (RandomService, _$log_, FnService) {
rnd = RandomService;
$log = _$log_;
fs = FnService;
}));
// interesting use of a custom matcher...
beforeEach(function () {
jasmine.addMatchers({
toBeWithinOf: function () {
return {
compare: function (actual, distance, base) {
var lower = base - distance,
upper = base + distance,
result = {};
result.pass = Math.abs(actual - base) <= distance;
if (result.pass) {
// for negation with ".not"
result.message = 'Expected ' + actual +
' to be outside ' + lower + ' and ' +
upper + ' (inclusive)';
} else {
result.message = 'Expected ' + actual +
' to be between ' + lower + ' and ' +
upper + ' (inclusive)';
}
return result;
}
}
}
});
});
it('should define RandomService', function () {
expect(rnd).toBeDefined();
});
it('should define api functions', function () {
expect(fs.areFunctions(rnd, [
'spread', 'randDim'
])).toBeTruthy();
});
// really, can only do this heuristically.. hope this doesn't break
it('should spread results across the range', function () {
var load = 1000,
s = 12,
low = 0,
high = 0,
i, res,
which = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
minCount = load / s * 0.5; // generous error
for (i=0; i<load; i++) {
res = rnd.spread(s);
if (res < low) low = res;
if (res > high) high = res;
which[res + s/2]++;
}
expect(low).toBe(-6);
expect(high).toBe(5);
// check we got a good number of hits in each bucket
for (i=0; i<s; i++) {
expect(which[i]).toBeGreaterThan(minCount);
}
});
// really, can only do this heuristically.. hope this doesn't break
it('should choose results across the dimension', function () {
var load = 1000,
dim = 100,
low = 999,
high = 0,
i, res;
for (i=0; i<load; i++) {
res = rnd.randDim(dim);
if (res < low) low = res;
if (res > high) high = res;
expect(res).toBeWithinOf(36, 50);
}
});
});
......@@ -29,7 +29,7 @@ describe('factory: fw/util/theme.js', function() {
ts.init();
}));
it('should define MapService', function () {
it('should define ThemeService', function () {
expect(ts).toBeDefined();
});
......
......@@ -34,8 +34,11 @@ describe('factory: view/topo/topoForce.js', function() {
it('should define api functions', function () {
expect(fs.areFunctions(tfs, [
'initForce', 'resize', 'updateDeviceColors',
'toggleHosts', 'toggleOffline','cycleDeviceLabels', 'unpin',
'initForce', 'newDim', 'destroyForce',
'updateDeviceColors', 'toggleHosts', 'toggleOffline',
'cycleDeviceLabels', 'unpin',
'addDevice', 'updateDevice', 'removeDevice',
'addHost', 'updateHost', 'removeHost',
'addLink', 'updateLink', 'removeLink'
......
This diff is collapsed. Click to expand it.