Simon Hunt
Committed by Gerrit Code Review

TopoRegions: created skeleton Topo2 UI view for development of the "region-aware" topology.

 - Added initial event generation (layout/region/ etc.) -- WIP

Change-Id: I2f93eea7505ff0400085d7f67491f6b61231cb86
......@@ -89,6 +89,17 @@ public abstract class RequestHandler {
/**
* Sends a message back to the client.
*
* @param eventType message event type
* @param payload message payload
*/
protected void sendMessage(String eventType, ObjectNode payload) {
// TODO: remove sid
parent.connection().sendMessage(eventType, 0, payload);
}
/**
* Sends a message back to the client.
* Here, the message is preformatted; the assumption is it has its
* eventType, sid and payload attributes already filled in.
*
......
......@@ -17,6 +17,7 @@
package org.onosproject.ui.model.topo;
import com.google.common.base.MoreObjects;
import org.onosproject.cluster.NodeId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.region.RegionId;
......@@ -30,6 +31,8 @@ public class UiDevice extends UiNode {
private final Device device;
private RegionId regionId;
private NodeId masterId;
private boolean online;
/**
* Creates a new UI device.
......@@ -51,6 +54,24 @@ public class UiDevice extends UiNode {
this.regionId = regionId;
}
/**
* Sets the ID of the controller node that holds mastership for this device.
*
* @param masterId master identifier
*/
public void setMasterId(NodeId masterId) {
this.masterId = masterId;
}
/**
* Sets a flag indicating whether the backing device is online.
*
* @param online boolen flag
*/
public void setOnline(boolean online) {
this.online = online;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
......@@ -104,4 +125,32 @@ public class UiDevice extends UiNode {
public UiRegion uiRegion() {
return topology.findRegion(regionId);
}
/**
* Returns a string representation of the type of the backing device.
*
* @return the device type
*/
public String type() {
return device.type().toString().toLowerCase();
}
/**
* Returns a boolean indicating whether the backing device is online.
*
* @return true if device is online, false otherwise
*/
public boolean isOnline() {
return online;
}
/**
* Returns the identifier for the cluster member that has
* mastership over this device.
*
* @return master cluster member identifier
*/
public NodeId master() {
return masterId;
}
}
......
......@@ -20,4 +20,41 @@ package org.onosproject.ui.model.topo;
* Represents a node drawn on the topology view (region, device, host).
*/
abstract class UiNode extends UiElement {
/**
* Default "layer" tag.
*/
public static final String LAYER_DEFAULT = "def";
/**
* Packet layer tag.
*/
public static final String LAYER_PACKET = "pkt";
/**
* Optical layer tag.
*/
public static final String LAYER_OPTICAL = "opt";
private String layer = LAYER_DEFAULT;
/**
* Returns the tag for the "layer" that the node should be rendered in
* when viewed in the oblique view.
*
* @return the node's layer
*/
public String layer() {
return layer;
}
/**
* Sets this node's "layer", for layered rendering.
*
* @param layerTag the layer tag to set
*/
public void setLayer(String layerTag) {
layer = layerTag;
}
}
......
......@@ -22,7 +22,10 @@ import org.onosproject.net.HostId;
import org.onosproject.net.region.Region;
import org.onosproject.net.region.RegionId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
......@@ -37,6 +40,8 @@ public class UiRegion extends UiNode {
private final Set<HostId> hostIds = new HashSet<>();
private final Set<UiLinkId> uiLinkIds = new HashSet<>();
private final List<String> layerOrder = new ArrayList<>();
private final UiTopology topology;
private final Region region;
......@@ -50,6 +55,8 @@ public class UiRegion extends UiNode {
public UiRegion(UiTopology topology, Region region) {
this.topology = topology;
this.region = region;
// unless told otherwise, we'll use a single, default layer
layerOrder.add(UiNode.LAYER_DEFAULT);
}
@Override
......@@ -60,6 +67,17 @@ public class UiRegion extends UiNode {
}
/**
* Sets the layer order for this region.
* Typically, the {@code UiNode.LAYER_*} constants will be used here.
*
* @param layers the layers
*/
public void setLayerOrder(String... layers) {
layerOrder.clear();
Collections.addAll(layerOrder, layers);
}
/**
* Returns the identity of the region.
*
* @return region ID
......@@ -170,4 +188,19 @@ public class UiRegion extends UiNode {
public Set<UiLink> links() {
return topology.linkSet(uiLinkIds);
}
/**
* Returns the order in which layers should be rendered. Lower layers
* come earlier in the list. For example, to indicate that nodes in the
* optical layer should be rendered "below" nodes in the packet layer,
* this method should return:
* <pre>
* [UiNode.LAYER_OPTICAL, UiNode.LAYER_PACKET]
* </pre>
*
* @return layer ordering
*/
public List<String> layerOrder() {
return Collections.unmodifiableList(layerOrder);
}
}
......
......@@ -17,6 +17,7 @@
package org.onosproject.ui.model.topo;
import org.onosproject.net.region.Region;
import org.onosproject.net.region.RegionId;
/**
* Represents a specific "subset" of the UI model of the network topology
......@@ -41,6 +42,11 @@ public class UiTopoLayout {
this.parent = parent;
}
@Override
public String toString() {
return "{UiTopoLayout: " + id + "}";
}
/**
* Returns the UI layout identifier.
*
......@@ -51,7 +57,8 @@ public class UiTopoLayout {
}
/**
* Returns the backing region with which this layout is associated.
* Returns the backing region with which this layout is associated. Note
* that this may be null (for the root layout).
*
* @return backing region
*/
......@@ -60,6 +67,16 @@ public class UiTopoLayout {
}
/**
* Returns the identifier of the backing region. Will be null if the
* region is null.
*
* @return backing region identifier
*/
public RegionId regionId() {
return region == null ? null : region.id();
}
/**
* Returns the parent layout identifier.
*
* @return parent layout identifier
......
......@@ -23,8 +23,12 @@ import org.onosproject.net.region.RegionId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -46,6 +50,9 @@ public class UiTopology extends UiElement {
private static final Logger log = LoggerFactory.getLogger(UiTopology.class);
private static final Comparator<UiClusterMember> CLUSTER_MEMBER_COMPARATOR =
(o1, o2) -> o1.idAsString().compareTo(o2.idAsString());
// top level mappings of topology elements by ID
private final Map<NodeId, UiClusterMember> cnodeLookup = new HashMap<>();
......@@ -84,6 +91,18 @@ public class UiTopology extends UiElement {
linkLookup.clear();
}
/**
* Returns all the cluster members, sorted by their ID.
*
* @return all cluster members
*/
public List<UiClusterMember> allClusterMembers() {
List<UiClusterMember> members = new ArrayList<>(cnodeLookup.values());
Collections.sort(members, CLUSTER_MEMBER_COMPARATOR);
return members;
}
/**
* Returns the cluster member with the given identifier, or null if no
* such member exists.
......
......@@ -185,7 +185,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
private TimerTask summaryTask = null;
private boolean summaryRunning = false;
private boolean listenersRemoved = false;
private volatile boolean listenersRemoved = false;
@Override
......
......@@ -32,7 +32,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -51,11 +50,12 @@ import org.onosproject.ui.UiExtension;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiMessageHandlerFactory;
import org.onosproject.ui.UiPreferencesService;
import org.onosproject.ui.UiTopoOverlayFactory;
import org.onosproject.ui.UiTopoMap;
import org.onosproject.ui.UiTopoMapFactory;
import org.onosproject.ui.UiTopoOverlayFactory;
import org.onosproject.ui.UiView;
import org.onosproject.ui.UiViewHidden;
import org.onosproject.ui.UiTopoMap;
import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -120,7 +120,8 @@ public class UiExtensionManager
private final ObjectMapper mapper = new ObjectMapper();
private final ExecutorService eventHandlingExecutor =
Executors.newSingleThreadExecutor(Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Executors.newSingleThreadExecutor(
Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
// Creates core UI extension
private UiExtension createCoreExtension() {
......@@ -130,6 +131,10 @@ public class UiExtensionManager
new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
new UiView(PLATFORM, "processor", "Packet Processors", "nav_processors"),
new UiView(NETWORK, "topo", "Topology", "nav_topo"),
// FIXME: leave commented out for now, while still under development
// new UiView(NETWORK, "topo2", "New-Topo"),
new UiView(NETWORK, "device", "Devices", "nav_devs"),
new UiViewHidden("flow"),
new UiViewHidden("port"),
......@@ -145,6 +150,7 @@ public class UiExtensionManager
() -> ImmutableList.of(
new UserPreferencesMessageHandler(),
new TopologyViewMessageHandler(),
new Topo2ViewMessageHandler(),
new MapSelectorMessageHandler(),
new DeviceViewMessageHandler(),
new LinkViewMessageHandler(),
......
......@@ -122,6 +122,15 @@ public class UiWebSocket
}
/**
* Provides a reference to the topology session.
*
* @return topo session reference
*/
public UiTopoSession topoSession() {
return topoSession;
}
/**
* Issues a close on the connection.
*/
synchronized void close() {
......@@ -186,13 +195,13 @@ public class UiWebSocket
@Override
public void onMessage(String data) {
log.debug("onMessage: {}", data);
lastActive = System.currentTimeMillis();
try {
ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
String type = message.path(EVENT).asText(UNKNOWN);
UiMessageHandler handler = handlers.get(type);
if (handler != null) {
log.debug("RX message: {}", message);
handler.process(message);
} else {
log.warn("No GUI message handler for type {}", type);
......@@ -208,6 +217,7 @@ public class UiWebSocket
try {
if (connection.isOpen()) {
connection.sendMessage(message.toString());
log.debug("TX message: {}", message);
}
} catch (IOException e) {
log.warn("Unable to send message {} to GUI due to {}", message, e);
......
/*
* Copyright 2016-present 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.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.osgi.ServiceDirectory;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.incubator.net.PortStatisticsService;
import org.onosproject.incubator.net.tunnel.TunnelService;
import org.onosproject.mastership.MastershipService;
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.region.Region;
import org.onosproject.net.statistic.StatisticService;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLink;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiTopoLayout;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Facility for creating JSON messages to send to the topology view in the
* Web client.
*/
class Topo2Jsonifier {
private final ObjectMapper mapper = new ObjectMapper();
private ServiceDirectory directory;
private ClusterService clusterService;
private DeviceService deviceService;
private LinkService linkService;
private HostService hostService;
private MastershipService mastershipService;
private IntentService intentService;
private FlowRuleService flowService;
private StatisticService flowStatsService;
private PortStatisticsService portStatsService;
private TopologyService topologyService;
private TunnelService tunnelService;
/**
* Creates an instance with a reference to the services directory, so that
* additional information about network elements may be looked up on
* on the fly.
*
* @param directory service directory
*/
Topo2Jsonifier(ServiceDirectory directory) {
this.directory = checkNotNull(directory, "Directory cannot be null");
clusterService = directory.get(ClusterService.class);
deviceService = directory.get(DeviceService.class);
linkService = directory.get(LinkService.class);
hostService = directory.get(HostService.class);
mastershipService = directory.get(MastershipService.class);
intentService = directory.get(IntentService.class);
flowService = directory.get(FlowRuleService.class);
flowStatsService = directory.get(StatisticService.class);
portStatsService = directory.get(PortStatisticsService.class);
topologyService = directory.get(TopologyService.class);
tunnelService = directory.get(TunnelService.class);
}
private ObjectNode objectNode() {
return mapper.createObjectNode();
}
private ArrayNode arrayNode() {
return mapper.createArrayNode();
}
private String nullIsEmpty(Object o) {
return o == null ? "" : o.toString();
}
/**
* Returns a JSON representation of the cluster members (ONOS instances).
*
* @param instances the instance model objects
* @return a JSON representation of the data
*/
ObjectNode instances(List<UiClusterMember> instances) {
NodeId local = clusterService.getLocalNode().id();
ObjectNode payload = objectNode();
ArrayNode members = arrayNode();
payload.set("members", members);
for (UiClusterMember member : instances) {
members.add(json(member, member.id().equals(local)));
}
return payload;
}
private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
return objectNode()
.put("id", member.id().toString())
.put("ip", member.ip().toString())
.put("online", member.isOnline())
.put("ready", member.isReady())
.put("uiAttached", isUiAttached)
.put("switches", member.deviceCount());
}
/**
* Returns a JSON representation of the layout to use for displaying in
* the topology view.
*
* @param layout the layout to transform
* @return a JSON representation of the data
*/
ObjectNode layout(UiTopoLayout layout) {
return objectNode()
.put("id", layout.id().toString())
.put("parent", nullIsEmpty(layout.parent()))
.put("region", nullIsEmpty(layout.regionId()))
.put("regionName", regionName(layout.region()));
}
private String regionName(Region region) {
return region == null ? "" : region.name();
}
/**
* Returns a JSON representation of the region to display in the topology
* view.
*
* @param region the region to transform to JSON
* @return a JSON representation of the data
*/
ObjectNode region(UiRegion region) {
ObjectNode payload = objectNode();
if (region == null) {
payload.put("note", "no-region");
return payload;
}
payload.put("id", region.id().toString());
ArrayNode layerOrder = arrayNode();
payload.set("layerOrder", layerOrder);
region.layerOrder().forEach(layerOrder::add);
ArrayNode devices = arrayNode();
payload.set("devices", devices);
for (UiDevice device : region.devices()) {
devices.add(json(device));
}
ArrayNode hosts = arrayNode();
payload.set("hosts", hosts);
for (UiHost host : region.hosts()) {
hosts.add(json(host));
}
ArrayNode links = arrayNode();
payload.set("links", links);
for (UiLink link : region.links()) {
links.add(json(link));
}
return payload;
}
private ObjectNode json(UiDevice device) {
ObjectNode node = objectNode()
.put("id", device.id().toString())
.put("type", device.type())
.put("online", device.isOnline())
.put("master", device.master().toString())
.put("layer", device.layer());
// TODO: complete device details
// addLabels(node, device);
// addProps(node, device);
// addGeoLocation(node, device);
// addMetaUi(node, device);
return node;
}
private void addLabels(ObjectNode node, UiDevice device) {
}
private ObjectNode json(UiHost host) {
return objectNode()
.put("id", host.id().toString())
.put("layer", host.layer());
// TODO: complete host details
}
private ObjectNode json(UiLink link) {
return objectNode()
.put("id", link.id().toString());
// TODO: complete link details
}
}
/*
* Copyright 2016-present 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.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
import org.onlab.osgi.ServiceDirectory;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.List;
/*
NOTES:
The original topology view message handler was broken into two classes
TopologyViewMessageHandler, and TopologyViewMessageHandlerBase.
We do not need to follow that model necessarily. Starting with a
single class, and breaking it apart later if necessary.
Need to figure out the connection between this message handler and the
new way of doing things with UiTopoSession...
*/
/**
* Server-side component for interacting with the new "Region aware" topology
* view in the Web UI.
*/
public class Topo2ViewMessageHandler extends UiMessageHandler {
private final Logger log = LoggerFactory.getLogger(getClass());
// === Inbound event identifiers
private static final String TOPO2_START = "topo2Start";
private static final String TOPO2_STOP = "topo2Stop";
// === Outbound event identifiers
private static final String CURRENT_LAYOUT = "topo2CurrentLayout";
private static final String CURRENT_REGION = "topo2CurrentRegion";
private static final String ALL_INSTANCES = "topo2AllInstances";
private static final String TOPO_START_DONE = "topo2StartDone";
private UiTopoSession topoSession;
private Topo2Jsonifier t2json;
@Override
public void init(UiConnection connection, ServiceDirectory directory) {
super.init(connection, directory);
// get the topo session from the UiWebSocket
topoSession = ((UiWebSocket) connection).topoSession();
t2json = new Topo2Jsonifier(directory);
}
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
new Topo2Start(),
new Topo2Stop()
);
}
// ==================================================================
private final class Topo2Start extends RequestHandler {
private Topo2Start() {
super(TOPO2_START);
}
@Override
public void process(long sid, ObjectNode payload) {
// client view is ready to receive data to display; so start up
// server-side processing, and send over initial state
log.debug("topo2Start: {}", payload);
List<UiClusterMember> instances = topoSession.getAllInstances();
sendMessage(ALL_INSTANCES, t2json.instances(instances));
UiTopoLayout currentLayout = topoSession.currentLayout();
sendMessage(CURRENT_LAYOUT, t2json.layout(currentLayout));
UiRegion region = topoSession.getRegion(currentLayout);
sendMessage(CURRENT_REGION, t2json.region(region));
// TODO: send information about devices/hosts/links in non-region
// TODO: send information about "linked, peer" regions
sendMessage(TOPO_START_DONE, null);
// OLD CODE DID THE FOLLOWING...
// addListeners();
// sendAllInstances(null);
// sendAllDevices();
// sendAllLinks();
// sendAllHosts();
// sendTopoStartDone();
}
}
private final class Topo2Stop extends RequestHandler {
private Topo2Stop() {
super(TOPO2_STOP);
}
@Override
public void process(long sid, ObjectNode payload) {
// client view has gone away; so shut down server-side processing
// TODO: implement...
log.debug("topo2Stop: {}", payload);
// OLD CODE DID THE FOLLOWING...
// removeListeners();
// stopSummaryMonitoring();
// traffic.stopMonitoring();
}
}
}
......@@ -21,10 +21,14 @@ import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.impl.topo.model.UiModelEvent;
import org.onosproject.ui.impl.topo.model.UiModelListener;
import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Coordinates with the {@link UiTopoLayoutService} to access
* {@link UiTopoLayout}s, and with the {@link UiSharedTopologyModel} which
......@@ -34,6 +38,10 @@ import org.slf4j.LoggerFactory;
* Note that an instance of this class will be created for each
* {@link UiWebSocket} connection, and will contain
* the state of how the topology is laid out for the logged-in user.
* <p>
* The expected pattern is for the {@link Topo2ViewMessageHandler} to obtain
* a reference to the session instance (via the {@link UiWebSocket}), and
* interact with it when topo-related events come in from the client.
*/
public class UiTopoSession implements UiModelListener {
private final Logger log = LoggerFactory.getLogger(getClass());
......@@ -129,4 +137,23 @@ public class UiTopoSession implements UiModelListener {
public void enableEvent(boolean enabled) {
messagesEnabled = enabled;
}
/**
* Returns the list of ONOS instances (cluster members).
*
* @return the list of ONOS instances
*/
public List<UiClusterMember> getAllInstances() {
return sharedModel.getClusterMembers();
}
/**
* Returns the region for the specified layout.
*
* @param layout layout filter
* @return region that the layout is based upon
*/
public UiRegion getRegion(UiTopoLayout layout) {
return sharedModel.getRegion(layout);
}
}
......
......@@ -41,6 +41,7 @@ import org.onosproject.ui.model.topo.UiTopology;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
......@@ -153,6 +154,10 @@ class ModelCache {
}
}
List<UiClusterMember> getAllClusterMembers() {
return uiTopology.allClusterMembers();
}
// === MASTERSHIP CHANGES
......@@ -199,7 +204,7 @@ class ModelCache {
// package private for unit test access
UiRegion accessRegion(RegionId id) {
return uiTopology.findRegion(id);
return id == null ? null : uiTopology.findRegion(id);
}
// invoked from UiSharedTopologyModel region listener
......@@ -484,4 +489,5 @@ class ModelCache {
public int hostCount() {
return uiTopology.hostCount();
}
}
......
......@@ -16,9 +16,6 @@
package org.onosproject.ui.impl.topo.model;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -64,9 +61,19 @@ import org.onosproject.net.statistic.StatisticService;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.ui.impl.topo.UiTopoSession;
import org.onosproject.ui.model.ServiceBundle;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiElement;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Service that creates and maintains the UI-model of the network topology.
*/
......@@ -190,6 +197,41 @@ public final class UiSharedTopologyModel
removeListener(session);
}
// =======================================================================
// methods that the topo session will use to extract information from us
/**
* Returns the list of cluster members stored in our model cache.
*
* @return list of cluster members
*/
public List<UiClusterMember> getClusterMembers() {
return cache.getAllClusterMembers();
}
public Set<UiElement> getElements(UiTopoLayout layout) {
Set<UiElement> results = new HashSet<>();
// TODO: figure out how to extract the appropriate nodes
// from the cache, for the given layout.
return results;
}
/**
* Returns the region for the given layout.
*
* @param layout layout filter
* @return the region the layout is based upon
*/
public UiRegion getRegion(UiTopoLayout layout) {
return cache.accessRegion(layout.regionId());
}
// =====================================================================
/**
* Default implementation of service bundle to return references to our
* dynamically injected services.
......
......@@ -256,6 +256,8 @@
});
}
// TODO: simplify listener handling (see theme.js for sample code)
function addOpenListener(callback) {
var id = nextListenerId++,
cb = fs.isF(callback),
......
/*
* Copyright 2016-present 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 View (theme) -- CSS file
*/
/* --- Base SVG Layer --- */
#ov-topo2 svg {
/*background-color: #f4f4f4;*/
background-color: goldenrod; /* just for testing */
}
/* --- "No Devices" Layer --- */
#ov-topo2 svg .noDevsBird {
fill: #db7773;
}
#ov-topo2 svg #topo2-noDevsLayer text {
fill: #7e9aa8;
}
/* --- Topo Map --- */
#ov-topo2 svg #topo2-map {
stroke-width: 2px;
stroke: #f4f4f4;
fill: #e5e5e6;
}
/*
* Copyright 2016-present 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 View (layout) -- CSS file
*/
/* --- Base SVG Layer --- */
#ov-topo2 svg {
/* prevents the little cut/copy/paste square that would appear on iPad */
-webkit-user-select: none;
}
<!-- Topology View partial HTML -->
<div id="ov-topo2">
<svg viewBox="0 0 1000 1000"
resize offset-height="56" offset-width="12"
notifier="notifyResize()">
</svg>
</div>
/*
* Copyright 2016-present 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 View Module
NOTE: currently under development to support Regions.
*/
(function () {
'use strict';
// references to injected services
var $scope, $log, $loc,
fs, mast, ks, zs,
gs, ms, sus, flash,
wss, ps, th,
t2es, t2fs;
// DOM elements
var ovtopo2, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
// Internal state
var zoomer, actionMap;
// === Helper Functions
// callback invoked when the SVG view has been resized..
function svgResized(s) {
$log.debug("topo2 view resized", s);
}
function setUpKeys(overlayKeys) {
$log.debug('topo2: set up keys....');
}
// === Controller Definition -----------------------------------------
angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote'])
.controller('OvTopo2Ctrl',
['$scope', '$log', '$location',
'FnService', 'MastService', 'KeyService', 'ZoomService',
'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
'WebSocketService', 'PrefsService', 'ThemeService',
'Topo2EventService', 'Topo2ForceService',
function (_$scope_, _$log_, _$loc_,
_fs_, _mast_, _ks_, _zs_,
_gs_, _ms_, _sus_, _flash_,
_wss_, _ps_, _th_,
_t2es_, _t2fs_) {
var params = _$loc_.search(),
projection,
dim,
wh,
uplink = {
// provides function calls back into this space
// showNoDevs: showNoDevs,
// projection: function () { return projection; },
// zoomLayer: function () { return zoomLayer; },
// zoomer: function () { return zoomer; },
// opacifyMap: opacifyMap,
// topoStartDone: topoStartDone
};
$scope = _$scope_;
$log = _$log_;
$loc = _$loc_;
fs = _fs_;
mast = _mast_;
ks = _ks_;
zs = _zs_;
gs = _gs_;
ms = _ms_;
sus = _sus_;
flash = _flash_;
wss = _wss_;
ps = _ps_;
th = _th_;
t2es = _t2es_;
t2fs = _t2fs_;
// capture selected intent parameters (if they are set in the
// query string) so that the traffic overlay can highlight
// the path for that intent
if (params.intentKey && params.intentAppId && params.intentAppName) {
$scope.intentData = {
key: params.intentKey,
appId: params.intentAppId,
appName: params.intentAppName
};
}
$scope.notifyResize = function () {
svgResized(fs.windowSize(mast.mastHeight()));
};
// Cleanup on destroyed scope..
$scope.$on('$destroy', function () {
$log.log('OvTopo2Ctrl is saying Buh-Bye!');
t2es.stop();
ks.unbindKeys();
t2fs.destroy();
});
// svg layer and initialization of components
ovtopo2 = d3.select('#ov-topo2');
svg = ovtopo2.select('svg');
// set the svg size to match that of the window, less the masthead
wh = fs.windowSize(mast.mastHeight());
$log.debug('setting topo SVG size to', wh);
svg.attr(wh);
dim = [wh.width, wh.height];
// set up our keyboard shortcut bindings
setUpKeys();
// make sure we can respond to topology events from the server
t2es.bindHandlers();
// initialize the force layout, ready to render the topology
t2fs.init();
// =-=-=-=-=-=-=-=-
// TODO: in future, we will load background map data
// asynchronously (hence the promise) and then chain off
// there to send the topo2start event to the server.
// For now, we'll send the event inline...
t2es.start();
// === ORIGINAL CODE ===
// setUpKeys();
// setUpToolbar();
// setUpDefs();
// setUpZoom();
// setUpNoDevs();
/*
setUpMap().then(
function (proj) {
var z = ps.getPrefs('topo_zoom', { tx:0, ty:0, sc:1 });
zoomer.panZoom([z.tx, z.ty], z.sc);
$log.debug('** Zoom restored:', z);
projection = proj;
$log.debug('** We installed the projection:', proj);
flash.enable(false);
toggleMap(prefsState.bg);
flash.enable(true);
mapShader(true);
// now we have the map projection, we are ready for
// the server to send us device/host data...
tes.start();
// need to do the following so we immediately get
// the summary panel data back from the server
restoreSummaryFromPrefs();
}
);
*/
// tes.bindHandlers();
// setUpSprites();
// forceG = zoomLayer.append('g').attr('id', 'topo-force');
// tfs.initForce(svg, forceG, uplink, dim);
// tis.initInst({ showMastership: tfs.showMastership });
// tps.initPanels();
// restoreConfigFromPrefs();
// ttbs.setDefaultOverlay(prefsState.ovidx);
// $log.debug('registered overlays...', tov.list());
$log.log('OvTopo2Ctrl has been created');
}]);
}());
/*
* Copyright 2016-present 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 Event Module.
Defines the conduit between the client and the server:
- provides a clean API for sending events to the server
- dispatches incoming events from the server to the appropriate sub-module
*/
(function () {
'use strict';
// injected refs
var $log, wss, t2fs;
// internal state
var handlerMap,
openListener;
// TODO: only add heartbeat timer etc. if we really need to be doing that..
// ========================== Helper Functions
function createHandlerMap() {
handlerMap = {
topo2AllInstances: t2fs,
topo2CurrentLayout: t2fs,
topo2CurrentRegion: t2fs,
topo2StartDone: t2fs
// Add further event names / module references as needed
};
}
function wsOpen(host, url) {
$log.debug('topo2Event: WSopen - cluster node:', host, 'URL:', url);
// tell the server we are ready to receive topo events
wss.sendEvent('topo2Start');
}
// bind our event handlers to the web socket service, so that our
// callbacks get invoked for incoming events
function bindHandlers() {
wss.bindHandlers(handlerMap);
$log.debug('topo2 event handlers bound');
}
// tell the server we are ready to receive topology events
function start() {
// in case we fail over to a new server,
// listen for wsock-open events
openListener = wss.addOpenListener(wsOpen);
wss.sendEvent('topo2Start');
$log.debug('topo2 comms started');
}
// tell the server we no longer wish to receive topology events
function stop() {
wss.sendEvent('topo2Stop');
wss.unbindHandlers(handlerMap);
wss.removeOpenListener(openListener);
openListener = null;
$log.debug('topo2 comms stopped');
}
// ========================== Main Service Definition
angular.module('ovTopo2')
.factory('Topo2EventService',
['$log', 'WebSocketService', 'Topo2ForceService',
function (_$log_, _wss_, _t2fs_) {
$log = _$log_;
wss = _wss_;
t2fs = _t2fs_;
// deferred creation of handler map, so module references are good
createHandlerMap();
return {
bindHandlers: bindHandlers,
start: start,
stop: stop
};
}]);
}());
/*
* Copyright 2016-present 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 Force Module.
Visualization of the topology in an SVG layer, using a D3 Force Layout.
*/
(function () {
'use strict';
// injected refs
var $log, wss;
// ========================== Helper Functions
function init() {
$log.debug('Initialize topo force layout');
}
function destroy() {
$log.debug('Destroy topo force layout');
}
// ========================== Event Handlers
function allInstances(data) {
$log.debug('>> topo2AllInstances event:', data)
}
function currentLayout(data) {
$log.debug('>> topo2CurrentLayout event:', data)
}
function currentRegion(data) {
$log.debug('>> topo2CurrentRegion event:', data)
}
function startDone(data) {
$log.debug('>> topo2StartDone event:', data)
}
// ========================== Main Service Definition
angular.module('ovTopo2')
.factory('Topo2ForceService',
['$log', 'WebSocketService',
function (_$log_, _wss_) {
$log = _$log_;
wss = _wss_;
return {
init: init,
destroy: destroy,
topo2AllInstances: allInstances,
topo2CurrentLayout: currentLayout,
topo2CurrentRegion: currentRegion,
topo2StartDone: startDone
};
}]);
}());
......@@ -126,6 +126,13 @@
<link rel="stylesheet" href="app/fw/widget/table.css">
<link rel="stylesheet" href="app/fw/widget/table-theme.css">
<!-- Under development for Region support. -->
<script src="app/view/topo2/topo2.js"></script>
<script src="app/view/topo2/topo2Event.js"></script>
<script src="app/view/topo2/topo2Force.js"></script>
<link rel="stylesheet" href="app/view/topo2/topo2.css">
<link rel="stylesheet" href="app/view/topo2/topo2-theme.css">
<!-- Builtin views javascript. -->
<script src="app/view/topo/topo.js"></script>
<script src="app/view/topo/topoD3.js"></script>
......