Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/foundation-2017' into jw/f17-dev…
Browse files Browse the repository at this point in the history
…elop-topo
  • Loading branch information
Jesse White committed Jun 2, 2017
2 parents 6301347 + f2b5be3 commit a07871f
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ protected void initializePool(final JdbcDataSource dataSource) throws SQLExcepti
config.setUsername(dataSource.getUserName());
config.setPassword(dataSource.getPassword());
config.setDriverClassName(dataSource.getClassName());
// NMS-9387: Block indefinitely when waiting for a connection
config.setConnectionTimeout(0);
config.setRegisterMbeans(true); // For JMX Monitoring
config.validate();
m_pool = new HikariDataSource(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ public List<SearchResult> query(SearchQuery searchQuery, GraphContainer graphCon
LOG.debug("SearchProvider->query: adding IPLIKE search spec '{}' to the search results.", queryString);
SearchResult searchResult = new SearchResult(getSearchProviderNamespace(), queryString, queryString,
queryString, SearchResult.COLLAPSIBLE, !SearchResult.COLLAPSED);
results.add(searchResult);
if (!results.contains(searchResult)) {
results.add(searchResult);
}
}
}

Expand All @@ -151,8 +153,10 @@ public List<SearchResult> query(SearchQuery searchQuery, GraphContainer graphCon
continue IPLOOP;

} else {
results.add(createSearchResult(ip, queryString));

SearchResult searchResult = createSearchResult(ip, queryString);
if (!results.contains(searchResult)) {
results.add(searchResult);
}
}
}
LOG.info("SearchProvider->query: found: '{}' IP interfaces.", ips.size());
Expand All @@ -163,7 +167,7 @@ public List<SearchResult> query(SearchQuery searchQuery, GraphContainer graphCon
}

LOG.info("SearchProvider->query: built search result with {} results.", results.size());

return results;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -77,6 +82,23 @@ public Point2D transform(V arg0) {
private transient Point m_currentPoint = new Point();
private transient Set<V> alreadyDone = new HashSet<V>();

/**
* Used for sorting vertices from left to right
*/
private final Comparator<V> pointBasedComparator = new Comparator<V>() {
@Override
public int compare(V v1, V v2) {
final Point2D p1 = locations.get(v1);
final Point2D p2 = locations.get(v2);

int xcomp = Double.compare(p1.getX(), p2.getX());
if (xcomp != 0) {
return xcomp;
}
return Double.compare(p1.getY(), p2.getY());
}
};

/**
* Creates an instance for the specified graph, X distance, and Y distance.
*/
Expand All @@ -92,7 +114,8 @@ public HierarchyLayout(Graph<V, E> g, int distx, int disty) {
private Set<V> getRoots() {
Set<V> roots = graph.getVertices().stream()
.filter(v -> graph.getInEdges(v).isEmpty())
.collect(Collectors.toSet());
// Preserve the order of the roots
.collect(Collectors.toCollection(LinkedHashSet::new));
return roots;
}

Expand Down Expand Up @@ -172,6 +195,63 @@ private void setCurrentPositionFor(V vertex) {
locations.get(vertex).setLocation(m_currentPoint);
}

/**
* Shifts the vertices horizontally ensuring that there is no more
* than "distx" units between each column of vertices.
*
* The resulting layout is a denser visual that maintains the existing
* structure of the original layout.
*
* For the purposes of this algorithm, vertices with the same value
* of the X coordinate are deemed to be in the same "column".
*
* @param vertices list of vertices that will be displayed
*/
public void horizontalSqueeze(List<V> vertices) {
// Determine the target distance between the column
final double targetDelta = this.distX;

// Order the list of vertices based on their current
// position in the X-Y plane, from left to right
final List<V> orderedVertices = new ArrayList<>(vertices);
Collections.sort(orderedVertices, pointBasedComparator);

Double setXTo = null;
Double lastX = null;
for (V v : orderedVertices) {
final Point2D currentPoint = locations.get(v);
final Double currentX = currentPoint.getX();

if (lastX == null) {
// This is the first vertex
// Capture the X value, and skip to the next one
lastX = setXTo = currentX;
} else if (Double.compare(lastX, currentX) == 0) {
// We're still in the same column as the last vertex
if (Double.compare(currentX, setXTo) != 0) {
locations.get(v).setLocation(setXTo, currentPoint.getY());
}
} else {
// We've hit the first vertex in a new column
// Calculate the distance between the current column and the last column
lastX = setXTo;
final double actualDelta = currentX - lastX;
if (Double.compare(actualDelta, targetDelta) <= 0) {
// The distance is <= the target delta, so we don't need to update
// anything in this column
setXTo = currentX;
} else {
// The distance is > the target delta, so we need to update
// all the X values for vertices in this column
setXTo = lastX + targetDelta;
// Update the X value for the current vertex
locations.get(v).setLocation(setXTo, currentPoint.getY());
}
lastX = currentX;
}
}
}

@Override
public Graph<V, E> getGraph() {
return graph;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void updateLayout(final Graph graph) {
// This should fix rendering if selected "Hierarchical Layout" is already selected, but the graph is not
// fully level aware. See NMS-8703
if (isFullyLevelAware(graph)) {
final edu.uci.ics.jung.algorithms.layout.Layout<VertexRef, Edge> treeLayout = createTreeLayout(graph);
final HierarchyLayout<VertexRef, Edge> treeLayout = createTreeLayout(graph);
applyLayoutPositions(graph.getDisplayVertices(), treeLayout, graphLayout);
} else {
// SEE NMS-8703
Expand Down Expand Up @@ -137,14 +137,19 @@ public int compare(Vertex o1, Vertex o2) {
return jungGraph;
}

private void applyLayoutPositions(final Collection<? extends Vertex> vertices, final edu.uci.ics.jung.algorithms.layout.Layout<VertexRef, Edge> layout, final Layout graphLayout) {
for(VertexRef v : vertices) {
private void applyLayoutPositions(final Collection<? extends Vertex> vertices, final HierarchyLayout<VertexRef, Edge> layout, final Layout graphLayout) {
final List<VertexRef> displayVertices = vertices.stream()
.map(v -> (VertexRef)v)
.collect(Collectors.toList());
layout.horizontalSqueeze(displayVertices);

for(VertexRef v : displayVertices) {
Point2D p = layout.transform(v);
graphLayout.setLocation(v, new Point(p.getX(), p.getY()));
}
}

private edu.uci.ics.jung.algorithms.layout.Layout<VertexRef, Edge> createTreeLayout(final Graph g) {
private HierarchyLayout<VertexRef, Edge> createTreeLayout(final Graph g) {
final edu.uci.ics.jung.graph.DirectedGraph<VertexRef, Edge> jungGraph = convert(g);
HierarchyLayout<VertexRef, Edge> layout = new HierarchyLayout<>(jungGraph, ELBOW_ROOM * 2, ELBOW_ROOM * 2);
return layout;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

package org.opennms.features.topology.app.internal.jung;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;

Expand All @@ -36,39 +38,128 @@
import org.opennms.features.topology.api.GraphContainer;
import org.opennms.features.topology.api.topo.AbstractEdge;
import org.opennms.features.topology.api.topo.Edge;
import org.opennms.features.topology.api.topo.LevelAware;
import org.opennms.features.topology.api.topo.Vertex;
import org.opennms.features.topology.app.internal.DefaultLayout;
import org.opennms.features.topology.app.internal.TestGraph;
import org.opennms.features.topology.app.internal.TestVertex;

public class HierarchyLayoutAlgorithmTest {
private static final double delta = 0.00001d;
private final List<Vertex> vertices = new ArrayList<>();
final List<Edge> edges = new ArrayList<>();
private DefaultLayout layout;

/**
* Generates the following graph:
*
* t1-v1
* / \
* t1-v2 t1-v3
*
* and verifies the resulting X and Y coordinates.
*/
@Test
public void basicLayout() {
// Vertices
vertices.add(new LevelAwareTestVertex("v1", 0));
vertices.add(new LevelAwareTestVertex("v2", 1));
vertices.add(new LevelAwareTestVertex("v3", 1));

// Edges
edges.add(new AbstractEdge("test", "v1-v2", vertices.get(0), vertices.get(1)));
edges.add(new AbstractEdge("test","v1-v3", vertices.get(0), vertices.get(2)));

buildLayout();

expectVertexToBeAt(0, 150.0d, 100.0d);
expectVertexToBeAt(1, 100.0d, 200.0d);
expectVertexToBeAt(2, 200.0d, 200.0d);
}

/**
* Generates the following graph:
*
* t1-v1 t2-v1
* / \
* t1-v2 t1-v3
* /
* t1-v4
*
* and verifies the resulting X and Y coordinates.
*/
@Test
public void notSoBasicLayout() {
// Vertices
vertices.add(new LevelAwareTestVertex("t1-v1", 0));
vertices.add(new LevelAwareTestVertex("t1-v2", 1));
vertices.add(new LevelAwareTestVertex("t1-v3", 1));
vertices.add(new LevelAwareTestVertex("t1-v4", 2));

vertices.add(new LevelAwareTestVertex("t2-v1", 0));

// Edges
edges.add(new AbstractEdge("test", "t1-v1-v2", vertices.get(0), vertices.get(1)));
edges.add(new AbstractEdge("test","t1-v1-v3", vertices.get(0), vertices.get(2)));
edges.add(new AbstractEdge("test","t1-v2-v4", vertices.get(1), vertices.get(3)));

buildLayout();

expectVertexToBeAt(0, 150.0d, 100.0d);
expectVertexToBeAt(1, 100.0d, 200.0d);
expectVertexToBeAt(2, 200.0d, 200.0d);
expectVertexToBeAt(3, 100.0d, 300.0d);
expectVertexToBeAt(4, 300.0d, 100.0d);
}

// NMS-8703
@Test
public void verifyHierarchyLayoutFallBack() {
// Vertices
final List<Vertex> vertices = new ArrayList<>();
vertices.add(new TestVertex("root"));
vertices.add(new TestVertex("v1"));
vertices.add(new TestVertex("v2"));
vertices.add(new TestVertex("v3"));

// Edges
final List<Edge> edges = new ArrayList<>();
edges.add(new AbstractEdge("test", "rootEdge", vertices.get(0), vertices.get(1)));
edges.add(new AbstractEdge("test","e1", vertices.get(1), vertices.get(2)));
edges.add(new AbstractEdge("test","e2", vertices.get(2), vertices.get(3)));

// Circle
edges.add(new AbstractEdge("test","e3", vertices.get(3), vertices.get(1)));

// Update the layouts and ensure no exception is thrown
buildLayout();
}

private void buildLayout() {
// Mock ALL the things
final GraphContainer mockGraphContainer = Mockito.mock(GraphContainer.class);
final DefaultLayout layout = new DefaultLayout();
layout = new DefaultLayout();
final TestGraph testGraph = new TestGraph(layout, vertices, edges);
Mockito.when(mockGraphContainer.getGraph()).thenReturn(testGraph);

// Update layouts and ensure no exception is thrown
new HierarchyLayoutAlgorithm().updateLayout(testGraph);
}

private void expectVertexToBeAt(int idx, double x, double y) {
assertEquals(x, layout.getLocation(vertices.get(idx)).getX(), delta);
assertEquals(y, layout.getLocation(vertices.get(idx)).getY(), delta);
}

private static class LevelAwareTestVertex extends TestVertex implements LevelAware {
private final int level;

public LevelAwareTestVertex(String id, int level) {
super(id);
this.level = level;
}

@Override
public int getLevel() {
return level;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ If the `assetLayers` property is defined as empty then a single graph layer will

=== Node filtering
In many cases it is desirable to control which nodes are included or excluded from a topology. For instance it is
useful to be able to generate customised topologies for specific customers which include only regions/sites etc
useful to be able to generate customised topologies for specific customers which include only regions/buildings etc
relevant to their filtered node set. To this end it is possible to define a node filter
which chooses which nodes are included in a generated topology.

Expand All @@ -99,31 +99,31 @@ Filters are defined using the same asset table keys which are available for the
|===
| Operation | Definition | Example
| OR | key1=value1,value2 alternatively key1=value1;key1=value2 | asset-region=north,south
| AND | key1=val1;key2=val2 | asset-region=north;asset-site=23
| NOT | key1=!val1 | asset-site=!23
| AND | key1=val1;key2=val2 | asset-region=north;asset-building=23
| NOT | key1=!val1 | asset-building=!23
|===

Thus the following configuration means include only nodes with region `north` or `south` but exclude all nodes with site `23`.
Thus the following configuration means include only nodes with region `north` or `south` but exclude all nodes with building `23`.
----
filter=asset-region=north,south;asset-site=!23
filter=asset-region=north,south;asset-building=!23
----
The filters are designed to treat all selected text key entries as comma separated values (csv). This allows OpenNMS node-categories which are
many to many entries to be dealt with as a comma separated list of values; routers,servers,web etc.
Thus we can select based on multiple separate node categories. The following configuration means show routers and servers on all sites except site 23.
Thus we can select based on multiple separate node categories. The following configuration means show routers and servers on all buildings except building 23.
----
filter=node-categories=routers,servers;asset-site=!23
filter=node-categories=routers,servers;asset-building=!23
----
The filters treat all asset table entries as comma seperated variables (csv). This also means that,
The filters treat all asset table entries as comma separated variables (csv). This also means that,
for instance asset-displaycategory could also contain several values separated by commas. e.g. customer1,customer2,customer3 etc.

NOTE: You should make sure asset addresses and other free format asset text fields do not contain commas if you want an exact match on the whole field

Regular expressions are also allowed. Regular expressions start with the ~ character.
You can also negate a regular expression by preceding it with !~.

The following example will match against regions 'Stuttgart' and 'Isengard' and any site name which ends in 4
The following example will match against regions 'Stuttgart' and 'Isengard' and any building name which ends in 4
----
filter=asset-region=~.*gar(t|d);asset-site=~.*4
filter=asset-region=~.*gar(t|d);asset-building=~.*4
----

=== Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ The following tools should be installed to follow this installation manual:

WARNING: By downloading the _Oracle Java SE Development Kit 8_ RPM installer, you will accept the license agreement
from _Oracle_ which can be found on the link:https://www.java.com/en/download/faq/distribution.xml[Java distribution] web site.

WARNING: Installing the _Java Runtime Environment (JRE)_ is not sufficient.
The development kit is often named _openjdk-devel_ or _openjdk-jdk_.
With a _JRE_ installed, _{opennms-product-name}_ will not start and throws a `java.lang.ClassNotFoundException: com.sun.tools.attach.AttachNotSupportedException`.
For more details see link:https://issues.opennms.org/browse/NMS-9327[NMS-9327].
Loading

0 comments on commit a07871f

Please sign in to comment.