Simon Hunt

ONOS-1479 -- GUI - augmenting topology view for extensibility: WIP.

- Major refactoring of TopologyViewMessageHandler and related classes.

Change-Id: I920f7f9f7317f3987a9a8da35ac086e9f8cab8d3
Showing 22 changed files with 1434 additions and 45 deletions
/*
* 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.
*
*/
package org.onosproject.ui.topo;
/**
* Partial implementation of the types of highlight to apply to topology
* elements.
*/
public abstract class AbstractHighlight {
private final TopoElementType type;
private final String elementId;
public AbstractHighlight(TopoElementType type, String elementId) {
this.type = type;
this.elementId = elementId;
}
public TopoElementType type() {
return type;
}
public String elementId() {
return elementId;
}
}
/*
* 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.
*
*/
package org.onosproject.ui.topo;
/**
* Denotes the types of highlight to apply to a link.
*/
public class DeviceHighlight extends AbstractHighlight {
public DeviceHighlight(String deviceId) {
super(TopoElementType.DEVICE, deviceId);
}
}
/*
* 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.
*
*/
package org.onosproject.ui.topo;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Encapsulates highlights to be applied to the topology view, such as
* highlighting links, displaying link labels, perhaps even decorating
* nodes with badges, etc.
*/
public class Highlights {
private static final DecimalFormat DF0 = new DecimalFormat("#,###");
private final Set<DeviceHighlight> devices = new HashSet<>();
private final Set<HostHighlight> hosts = new HashSet<>();
private final Set<LinkHighlight> links = new HashSet<>();
public Highlights add(DeviceHighlight d) {
devices.add(d);
return this;
}
public Highlights add(HostHighlight h) {
hosts.add(h);
return this;
}
public Highlights add(LinkHighlight lh) {
links.add(lh);
return this;
}
public Set<DeviceHighlight> devices() {
return Collections.unmodifiableSet(devices);
}
public Set<HostHighlight> hosts() {
return Collections.unmodifiableSet(hosts);
}
public Set<LinkHighlight> links() {
return Collections.unmodifiableSet(links);
}
}
/*
* 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.
*
*/
package org.onosproject.ui.topo;
/**
* Denotes the types of highlight to apply to a link.
*/
public class HostHighlight extends AbstractHighlight {
public HostHighlight(String hostId) {
super(TopoElementType.HOST, hostId);
}
}
/*
* 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.
*
*/
package org.onosproject.ui.topo;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Denotes the highlighting to be applied to a link.
* {@link Flavor} is a closed set of NO-, PRIMARY-, or SECONDARY- highlighting.
* {@link Mod} is an open ended set of additional modifications (CSS classes)
* to apply. Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are
* pre-defined mods.
* Label text may be set, which will also be displayed on the link.
*/
public class LinkHighlight extends AbstractHighlight {
private static final String PLAIN = "plain";
private static final String PRIMARY = "primary";
private static final String SECONDARY = "secondary";
private static final String EMPTY = "";
private static final String SPACE = " ";
private final Flavor flavor;
private final Set<Mod> mods = new TreeSet<>();
private String label = EMPTY;
/**
* Constructs a link highlight entity.
*
* @param linkId the link identifier
* @param flavor the highlight flavor
*/
public LinkHighlight(String linkId, Flavor flavor) {
super(TopoElementType.LINK, linkId);
this.flavor = checkNotNull(flavor);
}
/**
* Adds a highlighting modification to this link highlight.
*
* @param mod mod to be added
* @return self, for chaining
*/
public LinkHighlight addMod(Mod mod) {
mods.add(checkNotNull(mod));
return this;
}
/**
* Adds a label to be displayed on the link.
*
* @param label the label text
* @return self, for chaining
*/
public LinkHighlight setLabel(String label) {
this.label = label == null ? EMPTY : label;
return this;
}
/**
* Returns the highlight flavor.
*
* @return highlight flavor
*/
public Flavor flavor() {
return flavor;
}
/**
* Returns the highlight modifications.
*
* @return highlight modifications
*/
public Set<Mod> mods() {
return Collections.unmodifiableSet(mods);
}
/**
* Generates the CSS classes string from the {@link #flavor} and
* any optional {@link #mods}.
*
* @return CSS classes string
*/
public String cssClasses() {
StringBuilder sb = new StringBuilder(flavor.toString());
mods.forEach(m -> sb.append(SPACE).append(m));
return sb.toString();
}
/**
* Returns the label text.
*
* @return label text
*/
public String label() {
return label;
}
/**
* Link highlighting flavor.
*/
public enum Flavor {
NO_HIGHLIGHT(PLAIN),
PRIMARY_HIGHLIGHT(PRIMARY),
SECONDARY_HIGHLIGHT(SECONDARY);
private String cssName;
Flavor(String s) {
cssName = s;
}
@Override
public String toString() {
return cssName;
}
}
/**
* Link highlighting modification.
* <p>
* Note that this translates to a CSS class name that is applied to
* the link in the Topology UI.
*/
public static final class Mod implements Comparable<Mod> {
private final String modId;
public Mod(String modId) {
this.modId = checkNotNull(modId);
}
@Override
public String toString() {
return modId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Mod mod = (Mod) o;
return modId.equals(mod.modId);
}
@Override
public int hashCode() {
return modId.hashCode();
}
@Override
public int compareTo(Mod o) {
return this.modId.compareTo(o.modId);
}
}
/**
* Denotes a link to be tagged as an optical link.
*/
public static final Mod MOD_OPTICAL = new Mod("optical");
/**
* Denotes a link to be tagged with animated traffic ("marching ants").
*/
public static final Mod MOD_ANIMATED = new Mod("animated");
}
/*
* 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.
*
*/
package org.onosproject.ui.topo;
/**
* The topology element types to which a highlight can be applied.
*/
public enum TopoElementType {
DEVICE, HOST, LINK
}
......@@ -24,7 +24,8 @@ import org.onosproject.net.LinkKey;
import org.onosproject.net.link.LinkService;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.TopologyViewMessageHandlerBase.BiLink;
import org.onosproject.ui.impl.topo.BiLink;
import org.onosproject.ui.impl.topo.TopoUtils;
import org.onosproject.ui.table.TableModel;
import org.onosproject.ui.table.TableRequestHandler;
import org.onosproject.ui.table.cell.ConnectPointFormatter;
......@@ -33,13 +34,14 @@ import org.onosproject.ui.table.cell.EnumFormatter;
import java.util.Collection;
import java.util.Map;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.addLink;
/**
* Message handler for link view related messages.
*/
public class LinkViewMessageHandler extends UiMessageHandler {
private static final String A_BOTH_B = "A &harr; B";
private static final String A_SINGLE_B = "A &rarr; B";
private static final String LINK_DATA_REQ = "linkDataRequest";
private static final String LINK_DATA_RESP = "linkDataResponse";
private static final String LINKS = "links";
......@@ -94,38 +96,39 @@ public class LinkViewMessageHandler extends UiMessageHandler {
// First consolidate all uni-directional links into two-directional ones.
Map<LinkKey, BiLink> biLinks = Maps.newHashMap();
ls.getLinks().forEach(link -> addLink(biLinks, link));
ls.getLinks().forEach(link -> TopoUtils.addLink(biLinks, link));
// Now scan over all bi-links and produce table rows from them.
biLinks.values().forEach(biLink -> populateRow(tm.addRow(), biLink));
}
private void populateRow(TableModel.Row row, BiLink biLink) {
row.cell(ONE, biLink.one.src())
.cell(TWO, biLink.one.dst())
row.cell(ONE, biLink.one().src())
.cell(TWO, biLink.one().dst())
.cell(TYPE, linkType(biLink))
.cell(STATE, linkState(biLink))
.cell(DIRECTION, linkDir(biLink))
.cell(DURABLE, biLink.one.isDurable());
.cell(DURABLE, biLink.one().isDurable());
}
private String linkType(BiLink link) {
StringBuilder sb = new StringBuilder();
sb.append(link.one.type());
if (link.two != null && link.two.type() != link.one.type()) {
sb.append(" / ").append(link.two.type());
sb.append(link.one().type());
if (link.two() != null && link.two().type() != link.one().type()) {
sb.append(" / ").append(link.two().type());
}
return sb.toString();
}
private String linkState(BiLink link) {
return (link.one.state() == Link.State.ACTIVE ||
link.two.state() == Link.State.ACTIVE) ?
return (link.one().state() == Link.State.ACTIVE ||
link.two().state() == Link.State.ACTIVE) ?
ICON_ID_ONLINE : ICON_ID_OFFLINE;
}
private String linkDir(BiLink link) {
return link.two != null ? "A &harr; B" : "A &rarr; B";
return link.two() != null ? A_BOTH_B : A_SINGLE_B;
}
}
}
......
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.statistic.Load;
import org.onosproject.ui.topo.LinkHighlight;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
/**
* Representation of a link and its inverse, and any associated traffic data.
* This class understands how to generate {@link LinkHighlight}s for sending
* back to the topology view.
*/
public class BiLink {
private static final String EMPTY = "";
private final LinkKey key;
private final Link one;
private Link two;
private boolean hasTraffic = false;
private long bytes = 0;
private long rate = 0;
private long flows = 0;
private boolean isOptical = false;
private LinkHighlight.Flavor taggedFlavor = NO_HIGHLIGHT;
private boolean antMarch = false;
/**
* Constructs a bilink for the given key and initial link.
*
* @param key canonical key for this bilink
* @param link first link
*/
public BiLink(LinkKey key, Link link) {
this.key = key;
this.one = link;
}
/**
* Sets the second link for this bilink.
*
* @param link second link
*/
public void setOther(Link link) {
this.two = link;
}
/**
* Sets the optical flag to the given value.
*
* @param b true if an optical link
*/
public void setOptical(boolean b) {
isOptical = b;
}
/**
* Sets the ant march flag to the given value.
*
* @param b true if marching ants required
*/
public void setAntMarch(boolean b) {
antMarch = b;
}
/**
* Tags this bilink with a link flavor to be used in visual rendering.
*
* @param flavor the flavor to tag
*/
public void tagFlavor(LinkHighlight.Flavor flavor) {
this.taggedFlavor = flavor;
}
/**
* Adds load statistics, marks the bilink as having traffic.
*
* @param load load to add
*/
public void addLoad(Load load) {
addLoad(load, 0);
}
/**
* Adds load statistics, marks the bilink as having traffic, if the
* load rate is greater than the given threshold.
*
* @param load load to add
* @param threshold threshold to register traffic
*/
public void addLoad(Load load, double threshold) {
if (load != null) {
this.hasTraffic = hasTraffic || load.rate() > threshold;
this.bytes += load.latest();
this.rate += load.rate();
}
}
/**
* Adds the given count of flows to this bilink.
*
* @param count count of flows
*/
public void addFlows(int count) {
this.flows += count;
}
/**
* Generates a link highlight entity, based on state of this bilink.
*
* @param type the type of statistics to use to interpret the data
* @return link highlight data for this bilink
*/
public LinkHighlight generateHighlight(LinkStatsType type) {
switch (type) {
case FLOW_COUNT:
return highlightForFlowCount(type);
case FLOW_STATS:
case PORT_STATS:
return highlightForStats(type);
case TAGGED:
return highlightForTagging(type);
default:
throw new IllegalStateException("unexpected case: " + type);
}
}
private LinkHighlight highlightForStats(LinkStatsType type) {
return new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
.setLabel(generateLabel(type));
}
private LinkHighlight highlightForFlowCount(LinkStatsType type) {
LinkHighlight.Flavor flavor = flows() > 0 ?
PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
return new LinkHighlight(linkId(), flavor)
.setLabel(generateLabel(type));
}
private LinkHighlight highlightForTagging(LinkStatsType type) {
LinkHighlight hlite = new LinkHighlight(linkId(), flavor())
.setLabel(generateLabel(type));
if (isOptical()) {
hlite.addMod(LinkHighlight.MOD_OPTICAL);
}
if (isAntMarch()) {
hlite.addMod(LinkHighlight.MOD_ANIMATED);
}
return hlite;
}
// Generates a link identifier in the form that the Topology View on the
private String linkId() {
return TopoUtils.compactLinkString(one);
}
// Generates a string representation of the load, to be used as a label
private String generateLabel(LinkStatsType type) {
switch (type) {
case FLOW_COUNT:
return TopoUtils.formatFlows(flows());
case FLOW_STATS:
return TopoUtils.formatBytes(bytes());
case PORT_STATS:
return TopoUtils.formatBitRate(rate());
case TAGGED:
return hasTraffic() ? TopoUtils.formatBytes(bytes()) : EMPTY;
default:
return "?";
}
}
// === ----------------------------------------------------------------
// accessors
public LinkKey key() {
return key;
}
public Link one() {
return one;
}
public Link two() {
return two;
}
public boolean hasTraffic() {
return hasTraffic;
}
public boolean isOptical() {
return isOptical;
}
public boolean isAntMarch() {
return antMarch;
}
public LinkHighlight.Flavor flavor() {
return taggedFlavor;
}
public long bytes() {
return bytes;
}
public long rate() {
return rate;
}
public long flows() {
return flows;
}
}
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import org.onosproject.net.intent.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Encapsulates a selection of intents (paths) inferred from a selection
* of devices and/or hosts from the topology view.
*/
public class IntentSelection {
private static final int ALL = -1;
protected static final Logger log =
LoggerFactory.getLogger(IntentSelection.class);
private final NodeSelection nodes;
private final List<Intent> intents;
private int index = ALL;
/**
* Creates an intent selection group, based on selected nodes.
*
* @param nodes node selection
* @param filter intent filter
*/
public IntentSelection(NodeSelection nodes, TopologyViewIntentFilter filter) {
this.nodes = nodes;
intents = filter.findPathIntents(nodes.hosts(), nodes.devices());
}
/**
* Creates an intent selection group, for a single intent.
*
* @param intent the intent
*/
public IntentSelection(Intent intent) {
nodes = null;
intents = new ArrayList<>(1);
intents.add(intent);
index = 0;
}
/**
* Returns true if no intents are selected.
*
* @return true if nothing selected
*/
public boolean none() {
return intents.isEmpty();
}
/**
* Returns true if all intents in this select group are currently selected.
* This is the initial state, so that all intents are shown on the
* topology view with primary highlighting.
*
* @return true if all selected
*/
public boolean all() {
return index == ALL;
}
/**
* Returns true if there is a single intent in this select group, or if
* a specific intent has been marked (index != ALL).
*
* @return true if single intent marked
*/
public boolean single() {
return !all();
}
/**
* Returns the number of intents in this selection group.
*
* @return number of intents
*/
public int size() {
return intents.size();
}
/**
* Returns the index of the currently selected intent.
*
* @return the current index
*/
public int index() {
return index;
}
/**
* The list of intents in this selection group.
*
* @return list of intents
*/
public List<Intent> intents() {
return Collections.unmodifiableList(intents);
}
/**
* Marks and returns the next intent in this group. Note that the
* selection wraps around to the beginning again, if necessary.
*
* @return the next intent in the group
*/
public Intent next() {
index += 1;
if (index >= intents.size()) {
index = 0;
}
return intents.get(index);
}
/**
* Marks and returns the previous intent in this group. Note that the
* selection wraps around to the end again, if necessary.
*
* @return the previous intent in the group
*/
public Intent prev() {
index -= 1;
if (index < 0) {
index = intents.size() - 1;
}
return intents.get(index);
}
/**
* Returns the currently marked intent, or null if "all" intents
* are marked.
*
* @return the currently marked intent
*/
public Intent current() {
return all() ? null : intents.get(index);
}
@Override
public String toString() {
return "IntentSelection{" +
"nodes=" + nodes +
", #intents=" + intents.size() +
", index=" + index +
'}';
}
}
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
/**
* Designates type of stats to report on a highlighted link.
*/
public enum LinkStatsType {
/**
* Number of flows.
*/
FLOW_COUNT,
/**
* Number of bytes.
*/
FLOW_STATS,
/**
* Number of bits per second.
*/
PORT_STATS,
/**
* Custom tagged information.
*/
TAGGED
}
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.net.Device;
import org.onosproject.net.Host;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.HostService;
import org.onosproject.ui.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.HostId.hostId;
/**
* Encapsulates a selection of devices and/or hosts from the topology view.
*/
public class NodeSelection {
protected static final Logger log =
LoggerFactory.getLogger(NodeSelection.class);
private static final String IDS = "ids";
private static final String HOVER = "hover";
private final DeviceService deviceService;
private final HostService hostService;
private final Set<String> ids;
private final String hover;
private final Set<Device> devices = new HashSet<>();
private final Set<Host> hosts = new HashSet<>();
/**
* Creates a node selection entity, from the given payload, using the
* supplied device and host services.
*
* @param payload message payload
* @param deviceService device service
* @param hostService host service
*/
public NodeSelection(ObjectNode payload,
DeviceService deviceService,
HostService hostService) {
this.deviceService = deviceService;
this.hostService = hostService;
ids = extractIds(payload);
hover = extractHover(payload);
Set<String> unmatched = findDevices(ids);
unmatched = findHosts(unmatched);
if (unmatched.size() > 0) {
log.debug("Skipping unmatched IDs {}", unmatched);
}
if (!isNullOrEmpty(hover)) {
unmatched = new HashSet<>();
unmatched.add(hover);
unmatched = findDevices(unmatched);
unmatched = findHosts(unmatched);
if (unmatched.size() > 0) {
log.debug("Skipping unmatched HOVER {}", unmatched);
}
}
}
/**
* Returns a view of the selected devices.
*
* @return selected devices
*/
public Set<Device> devices() {
return Collections.unmodifiableSet(devices);
}
/**
* Returns a view of the selected hosts.
*
* @return selected hosts
*/
public Set<Host> hosts() {
return Collections.unmodifiableSet(hosts);
}
/**
* Returns true if nothing is selected.
*
* @return true if nothing selected
*/
public boolean none() {
return devices().size() == 0 && hosts().size() == 0;
}
@Override
public String toString() {
return "NodeSelection{" +
"ids=" + ids +
", hover='" + hover + '\'' +
", #devices=" + devices.size() +
", #hosts=" + hosts.size() +
'}';
}
// == helper methods
private Set<String> extractIds(ObjectNode payload) {
ArrayNode array = (ArrayNode) payload.path(IDS);
if (array == null || array.size() == 0) {
return Collections.emptySet();
}
Set<String> ids = new HashSet<>();
for (JsonNode node : array) {
ids.add(node.asText());
}
return ids;
}
private String extractHover(ObjectNode payload) {
return JsonUtils.string(payload, HOVER);
}
private Set<String> findDevices(Set<String> ids) {
Set<String> unmatched = new HashSet<>();
Device device;
for (String id : ids) {
try {
device = deviceService.getDevice(deviceId(id));
if (device != null) {
devices.add(device);
} else {
log.debug("Device with ID {} not found", id);
}
} catch (IllegalArgumentException e) {
unmatched.add(id);
}
}
return unmatched;
}
private Set<String> findHosts(Set<String> ids) {
Set<String> unmatched = new HashSet<>();
Host host;
for (String id : ids) {
try {
host = hostService.getHost(hostId(id));
if (host != null) {
hosts.add(host);
} else {
log.debug("Host with ID {} not found", id);
}
} catch (IllegalArgumentException e) {
unmatched.add(id);
}
}
return unmatched;
}
}
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import org.onosproject.incubator.net.PortStatisticsService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.statistic.StatisticService;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A bundle of services that the topology view requires to get its job done.
*/
public class ServicesBundle {
private final IntentService intentService;
private final DeviceService deviceService;
private final HostService hostService;
private final LinkService linkService;
private final FlowRuleService flowService;
private final StatisticService flowStatsService;
private final PortStatisticsService portStatsService;
/**
* Creates the services bundle.
* @param intentService intent service reference
* @param deviceService device service reference
* @param hostService host service reference
* @param linkService link service reference
* @param flowService flow service reference
* @param flowStatsService flow statistics service reference
* @param portStatsService port statistics service reference
*/
public ServicesBundle(IntentService intentService,
DeviceService deviceService,
HostService hostService,
LinkService linkService,
FlowRuleService flowService,
StatisticService flowStatsService,
PortStatisticsService portStatsService) {
this.intentService = checkNotNull(intentService);
this.deviceService = checkNotNull(deviceService);
this.hostService = checkNotNull(hostService);
this.linkService = checkNotNull(linkService);
this.flowService = checkNotNull(flowService);
this.flowStatsService = checkNotNull(flowStatsService);
this.portStatsService = checkNotNull(portStatsService);
}
public IntentService intentService() {
return intentService;
}
public DeviceService deviceService() {
return deviceService;
}
public HostService hostService() {
return hostService;
}
public LinkService linkService() {
return linkService;
}
public FlowRuleService flowService() {
return flowService;
}
public StatisticService flowStatsService() {
return flowStatsService;
}
public PortStatisticsService portStatsService() {
return portStatsService;
}
}
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import java.text.DecimalFormat;
import java.util.Map;
import static org.onosproject.net.LinkKey.linkKey;
/**
* Utility methods for helping out with the topology view.
*/
public final class TopoUtils {
public static final double KILO = 1024;
public static final double MEGA = 1024 * KILO;
public static final double GIGA = 1024 * MEGA;
public static final String GBITS_UNIT = "Gb";
public static final String MBITS_UNIT = "Mb";
public static final String KBITS_UNIT = "Kb";
public static final String BITS_UNIT = "b";
public static final String GBYTES_UNIT = "GB";
public static final String MBYTES_UNIT = "MB";
public static final String KBYTES_UNIT = "KB";
public static final String BYTES_UNIT = "B";
private static final DecimalFormat DF2 = new DecimalFormat("#,###.##");
private static final String COMPACT = "%s/%s-%s/%s";
private static final String EMPTY = "";
private static final String SPACE = " ";
private static final String PER_SEC = "ps";
private static final String FLOW = "flow";
private static final String FLOWS = "flows";
// non-instantiable
private TopoUtils() { }
/**
* Returns a compact identity for the given link, in the form
* used to identify links in the Topology View on the client.
*
* @param link link
* @return compact link identity
*/
public static String compactLinkString(Link link) {
return String.format(COMPACT, link.src().elementId(), link.src().port(),
link.dst().elementId(), link.dst().port());
}
/**
* Produces a canonical link key, that is, one that will match both a link
* and its inverse.
*
* @param link the link
* @return canonical key
*/
public static LinkKey canonicalLinkKey(Link link) {
String sn = link.src().elementId().toString();
String dn = link.dst().elementId().toString();
return sn.compareTo(dn) < 0 ?
linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
}
/**
* Returns human readable count of bytes, to be displayed as a label.
*
* @param bytes number of bytes
* @return formatted byte count
*/
public static String formatBytes(long bytes) {
String unit;
double value;
if (bytes > GIGA) {
value = bytes / GIGA;
unit = GBYTES_UNIT;
} else if (bytes > MEGA) {
value = bytes / MEGA;
unit = MBYTES_UNIT;
} else if (bytes > KILO) {
value = bytes / KILO;
unit = KBYTES_UNIT;
} else {
value = bytes;
unit = BYTES_UNIT;
}
return DF2.format(value) + SPACE + unit;
}
/**
* Returns human readable bit rate, to be displayed as a label.
*
* @param bytes bytes per second
* @return formatted bits per second
*/
public static String formatBitRate(long bytes) {
String unit;
double value;
//Convert to bits
long bits = bytes * 8;
if (bits > GIGA) {
value = bits / GIGA;
unit = GBITS_UNIT;
// NOTE: temporary hack to clip rate at 10.0 Gbps
// Added for the CORD Fabric demo at ONS 2015
// TODO: provide a more elegant solution to this issue
if (value > 10.0) {
value = 10.0;
}
} else if (bits > MEGA) {
value = bits / MEGA;
unit = MBITS_UNIT;
} else if (bits > KILO) {
value = bits / KILO;
unit = KBITS_UNIT;
} else {
value = bits;
unit = BITS_UNIT;
}
return DF2.format(value) + SPACE + unit + PER_SEC;
}
/**
* Returns human readable flow count, to be displayed as a label.
*
* @param flows number of flows
* @return formatted flow count
*/
public static String formatFlows(long flows) {
if (flows < 1) {
return EMPTY;
}
return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
}
/**
* Creates a new biLink with the supplied link (and adds it to the map),
* or attaches the link to an existing biLink (which already has the
* peer link).
*
* @param linkMap map of biLinks
* @param link the link to add
* @return the biLink to which the link was added
*/
// creates a new biLink with supplied link, or attaches link to the
// existing biLink (which already has its peer link)
public static BiLink addLink(Map<LinkKey, BiLink> linkMap, Link link) {
LinkKey key = TopoUtils.canonicalLinkKey(link);
BiLink biLink = linkMap.get(key);
if (biLink != null) {
biLink.setOther(link);
} else {
biLink = new BiLink(key, link);
linkMap.put(key, biLink);
}
return biLink;
}
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
package org.onosproject.ui.impl.topo;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
......@@ -56,32 +56,35 @@ public class TopologyViewIntentFilter {
private final LinkService linkService;
/**
* Crreates an intent filter.
* Creates an intent filter.
*
* @param intentService intent service reference
* @param deviceService device service reference
* @param hostService host service reference
* @param linkService link service reference
* @param services service references bundle
*/
TopologyViewIntentFilter(IntentService intentService, DeviceService deviceService,
HostService hostService, LinkService linkService) {
this.intentService = intentService;
this.deviceService = deviceService;
this.hostService = hostService;
this.linkService = linkService;
public TopologyViewIntentFilter(ServicesBundle services) {
this.intentService = services.intentService();
this.deviceService = services.deviceService();
this.hostService = services.hostService();
this.linkService = services.linkService();
}
// TODO: Review - do we need this signature, with sourceIntents??
// public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
// Iterable<Intent> sourceIntents) {
// }
/**
* Finds all path (host-to-host or point-to-point) intents that pertains
* to the given hosts.
* Finds all path (host-to-host or point-to-point) intents that pertain
* to the given hosts and devices.
*
* @param hosts set of hosts to query by
* @param devices set of devices to query by
* @param sourceIntents collection of intents to search
* @return set of intents that 'match' all hosts and devices given
*/
List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
Iterable<Intent> sourceIntents) {
public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
// start with all intents
Iterable<Intent> sourceIntents = intentService.getIntents();
// Derive from this the set of edge connect points.
Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
......
/*
* 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.
*
*/
package org.onosproject.ui.impl.topo;
import org.onosproject.net.intent.Intent;
import org.onosproject.ui.topo.LinkHighlight;
/**
* Auxiliary data carrier for assigning a highlight class to a set of
* intents, for visualization in the topology view.
*/
public class TrafficClass {
private final LinkHighlight.Flavor flavor;
private final Iterable<Intent> intents;
private final boolean showTraffic;
public TrafficClass(LinkHighlight.Flavor flavor, Iterable<Intent> intents) {
this(flavor, intents, false);
}
public TrafficClass(LinkHighlight.Flavor flavor, Iterable<Intent> intents,
boolean showTraffic) {
this.flavor = flavor;
this.intents = intents;
this.showTraffic = showTraffic;
}
public LinkHighlight.Flavor flavor() {
return flavor;
}
public Iterable<Intent> intents() {
return intents;
}
public boolean showTraffic() {
return showTraffic;
}
}
......@@ -58,6 +58,7 @@
showHosts = false, // whether hosts are displayed
showOffline = true, // whether offline devices are displayed
nodeLock = false, // whether nodes can be dragged or not (locked)
fTimer, // timer for delayed force layout
fNodesTimer, // timer for delayed nodes update
fLinksTimer, // timer for delayed links update
dim, // the dimensions of the force layout [w,h]
......@@ -117,6 +118,7 @@
network.nodes.push(d);
lu[id] = d;
updateNodes();
fStart();
}
function updateDevice(data) {
......@@ -170,6 +172,7 @@
lu[d.egress] = lnk;
updateLinks();
}
fStart();
}
function updateHost(data) {
......@@ -215,6 +218,7 @@
aggregateLink(d, data);
lu[d.key] = d;
updateLinks();
fStart();
}
}
......@@ -322,6 +326,7 @@
// remove from lookup cache
delete lu[removed[0].key];
updateLinks();
fResume();
}
}
......@@ -343,6 +348,7 @@
// NOTE: upd is false if we were called from removeDeviceElement()
if (upd) {
updateNodes();
fResume();
}
}
......@@ -367,6 +373,7 @@
// remove from SVG
updateNodes();
fResume();
}
function updateHostVisibility() {
......@@ -520,8 +527,9 @@
fNodesTimer = $timeout(_updateNodes, 150);
}
// IMPLEMENTATION NOTE: _updateNodes() should NOT stop, start, or resume
// the force layout; that needs to be determined and implemented elsewhere
function _updateNodes() {
force.stop();
// select all the nodes in the layout:
node = nodeG.selectAll('.node')
.data(network.nodes, function (d) { return d.id; });
......@@ -536,7 +544,10 @@
.attr({
id: function (d) { return sus.safeId(d.id); },
class: mkSvgClass,
transform: function (d) { return sus.translate(d.x, d.y); },
transform: function (d) {
// Need to guard against NaN here ??
return sus.translate(d.x, d.y);
},
opacity: 0
})
.call(drag)
......@@ -564,7 +575,6 @@
// exiting node specifics:
exiting.filter('.host').each(td3.hostExit);
exiting.filter('.device').each(td3.deviceExit);
fStart();
}
// ==========================
......@@ -659,9 +669,10 @@
fLinksTimer = $timeout(_updateLinks, 150);
}
// IMPLEMENTATION NOTE: _updateLinks() should NOT stop, start, or resume
// the force layout; that needs to be determined and implemented elsewhere
function _updateLinks() {
var th = ts.theme();
force.stop();
link = linkG.selectAll('.link')
.data(network.links, function (d) { return d.key; });
......@@ -714,7 +725,6 @@
})
.style('opacity', 0.0)
.remove();
fStart();
}
......@@ -729,14 +739,23 @@
function fStart() {
if (!tos.isOblique()) {
$log.debug("Starting force-layout");
force.start();
if (fTimer) {
$timeout.cancel(fTimer);
}
fTimer = $timeout(function () {
$log.debug("Starting force-layout");
force.start();
}, 200);
}
}
var tickStuff = {
nodeAttr: {
transform: function (d) { return sus.translate(d.x, d.y); }
transform: function (d) {
var dx = isNaN(d.x) ? 0 : d.x,
dy = isNaN(d.y) ? 0 : d.y;
return sus.translate(dx, dy);
}
},
linkAttr: {
x1: function (d) { return d.position.x1; },
......@@ -1046,6 +1065,9 @@
force = drag = null;
// clean up $timeout promises
if (fTimer) {
$timeout.cancel(fTimer);
}
if (fNodesTimer) {
$timeout.cancel(fNodesTimer);
}
......
......@@ -293,7 +293,7 @@
findLinkById( id )
*/
var paths = data.paths;
var paths = data.links;
api.clearLinkTrafficStyle();
api.removeLinkLabels();
......
......@@ -114,7 +114,7 @@
}
if (!ev.shiftKey) {
deselectAll();
deselectAll(true);
}
selections[obj.id] = { obj: obj, el: el };
......@@ -135,7 +135,7 @@
}
}
function deselectAll() {
function deselectAll(skipUpdate) {
var something = (selectOrder.length > 0);
// deselect all nodes in the network...
......@@ -143,7 +143,9 @@
selections = {};
selectOrder = [];
api.updateDeviceColors();
updateDetail();
if (!skipUpdate) {
updateDetail();
}
// return true if something was selected
return something;
......
......@@ -42,9 +42,9 @@
// invoked in response to change in selection and/or mouseover/out:
function requestTrafficForMode() {
if (hoverMode === 'flows') {
if (trafficMode === 'flows') {
requestDeviceLinkFlows();
} else if (hoverMode === 'intents') {
} else if (trafficMode === 'intents') {
requestRelatedIntents();
} else {
cancelTraffic();
......@@ -175,7 +175,6 @@
}
// === -----------------------------------------------------
// === MODULE DEFINITION ===
......