blob: fa426e18806a15f64444ee6568307e9f0d9f38d6 [file] [log] [blame]
/*
* 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.model;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.cluster.ControllerNode;
import org.onosproject.cluster.NodeId;
import org.onosproject.cluster.RoleInfo;
import org.onosproject.event.EventDispatcher;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.EdgeLink;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.region.Region;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.UiTopoLayoutService;
import org.onosproject.ui.impl.topo.Topo2Jsonifier;
import org.onosproject.ui.model.ServiceBundle;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiDeviceLink;
import org.onosproject.ui.model.topo.UiEdgeLink;
import org.onosproject.ui.model.topo.UiElement;
import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLinkId;
import org.onosproject.ui.model.topo.UiModelEvent;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.onosproject.ui.model.topo.UiTopoLayoutId;
import org.onosproject.ui.model.topo.UiTopology;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.CLUSTER_MEMBER_ADDED_OR_UPDATED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.CLUSTER_MEMBER_REMOVED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.DEVICE_ADDED_OR_UPDATED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.DEVICE_REMOVED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.HOST_ADDED_OR_UPDATED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.HOST_MOVED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.HOST_REMOVED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.LINK_ADDED_OR_UPDATED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.LINK_REMOVED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.REGION_ADDED_OR_UPDATED;
import static org.onosproject.ui.model.topo.UiModelEvent.Type.REGION_REMOVED;
import static org.onosproject.ui.model.topo.UiLinkId.uiLinkId;
/**
* UI Topology Model cache.
*/
class ModelCache {
private static final String E_NO_ELEMENT = "Tried to remove non-member {}: {}";
private static final String MEMO_ADDED = "added";
private static final String MEMO_UPDATED = "updated";
private static final String MEMO_REMOVED = "removed";
private static final String MEMO_MOVED = "moved";
private static final Logger log = LoggerFactory.getLogger(ModelCache.class);
// TODO: add NetworkConfigService to service bundle
// private final NetworkConfigService cfgService =
// DefaultServiceDirectory.getService(NetworkConfigService.class);
private final ServiceBundle services;
private final EventDispatcher dispatcher;
private final UiTopology uiTopology;
private Topo2Jsonifier t2json;
ModelCache(ServiceBundle services, EventDispatcher eventDispatcher) {
this.services = services;
this.dispatcher = eventDispatcher;
uiTopology = new UiTopology(services);
}
@Override
public String toString() {
return "ModelCache{" + uiTopology + "}";
}
private void postEvent(UiModelEvent.Type type, UiElement subject, String memo) {
ObjectNode data = t2json != null ? t2json.jsonUiElement(subject) : null;
dispatcher.post(new UiModelEvent(type, subject, data, memo));
}
void injectJsonifier(Topo2Jsonifier t2json) {
this.t2json = t2json;
}
void clear() {
uiTopology.clear();
}
/**
* Create our internal model of the global topology. An assumption we are
* making is that the topology is empty to start.
*/
void load() {
loadClusterMembers();
loadRegions();
loadDevices();
loadDeviceLinks();
loadHosts();
}
// === CLUSTER MEMBERS
private UiClusterMember addNewClusterMember(ControllerNode n) {
UiClusterMember member = new UiClusterMember(uiTopology, n);
uiTopology.add(member);
return member;
}
private void updateClusterMember(UiClusterMember member) {
ControllerNode.State state = services.cluster().getState(member.id());
member.setState(state);
}
private void loadClusterMembers() {
for (ControllerNode n : services.cluster().getNodes()) {
UiClusterMember member = addNewClusterMember(n);
updateClusterMember(member);
}
}
// invoked from UiSharedTopologyModel cluster event listener
void addOrUpdateClusterMember(ControllerNode cnode) {
NodeId id = cnode.id();
String memo = MEMO_UPDATED;
UiClusterMember member = uiTopology.findClusterMember(id);
if (member == null) {
member = addNewClusterMember(cnode);
memo = MEMO_ADDED;
}
updateClusterMember(member);
postEvent(CLUSTER_MEMBER_ADDED_OR_UPDATED, member, memo);
}
// package private for unit test access
UiClusterMember accessClusterMember(NodeId id) {
return uiTopology.findClusterMember(id);
}
// invoked from UiSharedTopologyModel cluster event listener
void removeClusterMember(ControllerNode cnode) {
NodeId id = cnode.id();
UiClusterMember member = uiTopology.findClusterMember(id);
if (member != null) {
uiTopology.remove(member);
postEvent(CLUSTER_MEMBER_REMOVED, member, MEMO_REMOVED);
} else {
log.warn(E_NO_ELEMENT, "cluster node", id);
}
}
List<UiClusterMember> getAllClusterMembers() {
return uiTopology.allClusterMembers();
}
// === MASTERSHIP CHANGES
// invoked from UiSharedTopologyModel mastership listener
void updateMasterships(DeviceId deviceId, RoleInfo roleInfo) {
// To think about:: do we need to store mastership info?
// or can we rely on looking it up live?
// TODO: store the updated mastership information
// TODO: post event
}
// === THE NULL REGION
UiRegion nullRegion() {
return uiTopology.nullRegion();
}
// === REGIONS
private UiRegion addNewRegion(Region r) {
UiRegion region = new UiRegion(uiTopology, r);
uiTopology.add(region);
log.debug("Region {} added to topology", region);
return region;
}
private void updateRegion(UiRegion region) {
RegionId rid = region.id();
Set<DeviceId> deviceIds = services.region().getRegionDevices(rid);
Set<HostId> hostIds = services.region().getRegionHosts(rid);
// Make sure device objects refer to their region
deviceIds.forEach(d -> {
UiDevice dev = uiTopology.findDevice(d);
if (dev != null) {
dev.setRegionId(rid);
} else {
// if we don't have the UiDevice in the topology, what can we do?
log.warn("Region device {}, but we don't have UiDevice in topology", d);
}
});
hostIds.forEach(d -> {
UiHost host = uiTopology.findHost(d);
if (host != null) {
host.setRegionId(rid);
} else {
// if we don't have the UiDevice in the topology, what can we do?
log.warn("Region host {}, but we don't have UiHost in topology", d);
}
});
// Make sure the region object refers to the devices
region.reconcileDevices(deviceIds);
region.reconcileHosts(hostIds);
fixupContainmentHierarchy(region);
}
private void fixupContainmentHierarchy(UiRegion region) {
UiTopoLayoutService ls = services.layout();
RegionId regionId = region.id();
UiTopoLayout layout = ls.getLayout(regionId);
if (layout == null) {
// no layout backed by this region
log.warn("No layout backed by region {}", regionId);
return;
}
UiTopoLayoutId layoutId = layout.id();
if (!layout.isRoot()) {
UiTopoLayoutId parentId = layout.parent();
UiTopoLayout parentLayout = ls.getLayout(parentId);
RegionId parentRegionId = parentLayout.regionId();
region.setParent(parentRegionId);
}
Set<UiTopoLayout> kids = ls.getChildren(layoutId);
Set<RegionId> kidRegionIds = new HashSet<>(kids.size());
kids.forEach(k -> kidRegionIds.add(k.regionId()));
region.setChildren(kidRegionIds);
}
private void loadRegions() {
for (Region r : services.region().getRegions()) {
UiRegion region = addNewRegion(r);
updateRegion(region);
}
}
// invoked from UiSharedTopologyModel region listener
void addOrUpdateRegion(Region region) {
RegionId id = region.id();
String memo = MEMO_UPDATED;
UiRegion uiRegion = uiTopology.findRegion(id);
if (uiRegion == null) {
uiRegion = addNewRegion(region);
memo = MEMO_ADDED;
}
updateRegion(uiRegion);
postEvent(REGION_ADDED_OR_UPDATED, uiRegion, memo);
}
// package private for unit test access
UiRegion accessRegion(RegionId id) {
return id == null ? null : uiTopology.findRegion(id);
}
// invoked from UiSharedTopologyModel region listener
void removeRegion(Region region) {
RegionId id = region.id();
UiRegion uiRegion = uiTopology.findRegion(id);
if (uiRegion != null) {
uiTopology.remove(uiRegion);
postEvent(REGION_REMOVED, uiRegion, MEMO_REMOVED);
} else {
log.warn(E_NO_ELEMENT, "region", id);
}
}
Set<UiRegion> getAllRegions() {
return uiTopology.allRegions();
}
// === DEVICES
private UiDevice addNewDevice(Device d) {
UiDevice device = new UiDevice(uiTopology, d);
updateDevice(device);
uiTopology.add(device);
log.debug("Device {} added to topology", device);
return device;
}
// make sure the UiDevice is tagged with the region it belongs to
private void updateDevice(UiDevice device) {
Region r = services.region().getRegionForDevice(device.id());
RegionId rid = r == null ? UiRegion.NULL_ID : r.id();
device.setRegionId(rid);
}
private void loadDevices() {
for (Device d : services.device().getDevices()) {
addNewDevice(d);
}
}
// invoked from UiSharedTopologyModel device listener
void addOrUpdateDevice(Device device) {
DeviceId id = device.id();
String memo = MEMO_UPDATED;
UiDevice uiDevice = uiTopology.findDevice(id);
if (uiDevice == null) {
uiDevice = addNewDevice(device);
memo = MEMO_ADDED;
} else {
updateDevice(uiDevice);
}
postEvent(DEVICE_ADDED_OR_UPDATED, uiDevice, memo);
}
// package private for unit test access
UiDevice accessDevice(DeviceId id) {
return uiTopology.findDevice(id);
}
// invoked from UiSharedTopologyModel device listener
void removeDevice(Device device) {
DeviceId id = device.id();
UiDevice uiDevice = uiTopology.findDevice(id);
if (uiDevice != null) {
uiTopology.remove(uiDevice);
postEvent(DEVICE_REMOVED, uiDevice, MEMO_REMOVED);
} else {
log.warn(E_NO_ELEMENT, "device", id);
}
}
Set<UiDevice> getAllDevices() {
return uiTopology.allDevices();
}
// === LINKS ===
private UiDeviceLink addNewDeviceLink(UiLinkId id) {
UiDeviceLink uiDeviceLink = new UiDeviceLink(uiTopology, id);
uiTopology.add(uiDeviceLink);
return uiDeviceLink;
}
private UiEdgeLink addNewEdgeLink(UiLinkId id) {
UiEdgeLink uiEdgeLink = new UiEdgeLink(uiTopology, id);
uiTopology.add(uiEdgeLink);
return uiEdgeLink;
}
private void updateDeviceLink(UiDeviceLink uiDeviceLink, Link link) {
uiDeviceLink.attachBackingLink(link);
}
private void loadDeviceLinks() {
for (Link link : services.link().getLinks()) {
UiLinkId id = uiLinkId(link);
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink == null) {
uiDeviceLink = addNewDeviceLink(id);
}
updateDeviceLink(uiDeviceLink, link);
}
}
// invoked from UiSharedTopologyModel link listener
void addOrUpdateDeviceLink(Link link) {
UiLinkId id = uiLinkId(link);
String memo = MEMO_UPDATED;
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink == null) {
uiDeviceLink = addNewDeviceLink(id);
memo = MEMO_ADDED;
}
updateDeviceLink(uiDeviceLink, link);
postEvent(LINK_ADDED_OR_UPDATED, uiDeviceLink, memo);
}
// package private for unit test access
UiDeviceLink accessDeviceLink(UiLinkId id) {
return uiTopology.findDeviceLink(id);
}
// invoked from UiSharedTopologyModel link listener
void removeDeviceLink(Link link) {
UiLinkId id = uiLinkId(link);
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink != null) {
boolean remaining = uiDeviceLink.detachBackingLink(link);
if (remaining) {
postEvent(LINK_ADDED_OR_UPDATED, uiDeviceLink, MEMO_UPDATED);
} else {
uiTopology.remove(uiDeviceLink);
postEvent(LINK_REMOVED, uiDeviceLink, MEMO_REMOVED);
}
} else {
log.warn(E_NO_ELEMENT, "Device link", id);
}
}
Set<UiDeviceLink> getAllDeviceLinks() {
return uiTopology.allDeviceLinks();
}
// === HOSTS
private EdgeLink synthesizeLink(Host h) {
return createEdgeLink(h, true);
}
private UiHost addNewHost(Host h) {
UiHost host = new UiHost(uiTopology, h);
uiTopology.add(host);
EdgeLink elink = synthesizeLink(h);
UiLinkId elinkId = uiLinkId(elink);
host.setEdgeLinkId(elinkId);
// add synthesized edge link to the topology
addNewEdgeLink(elinkId);
return host;
}
private void insertNewUiEdgeLink(UiLinkId id) {
addNewEdgeLink(id);
}
private void updateHost(UiHost uiHost, Host h) {
UiEdgeLink existing = uiTopology.findEdgeLink(uiHost.edgeLinkId());
// TODO: review - do we need EdgeLink now that we are creating from id only?
EdgeLink currentElink = synthesizeLink(h);
UiLinkId currentElinkId = uiLinkId(currentElink);
if (existing != null) {
if (!currentElinkId.equals(existing.id())) {
// edge link has changed
insertNewUiEdgeLink(currentElinkId);
uiHost.setEdgeLinkId(currentElinkId);
uiTopology.remove(existing);
}
} else {
// no previously existing edge link
insertNewUiEdgeLink(currentElinkId);
uiHost.setEdgeLinkId(currentElinkId);
}
HostLocation hloc = h.location();
uiHost.setLocation(hloc.deviceId(), hloc.port());
}
private void loadHosts() {
for (Host h : services.host().getHosts()) {
UiHost host = addNewHost(h);
updateHost(host, h);
}
}
// invoked from UiSharedTopologyModel host listener
void addOrUpdateHost(Host host) {
HostId id = host.id();
String memo = MEMO_UPDATED;
UiHost uiHost = uiTopology.findHost(id);
if (uiHost == null) {
uiHost = addNewHost(host);
memo = MEMO_ADDED;
}
updateHost(uiHost, host);
postEvent(HOST_ADDED_OR_UPDATED, uiHost, memo);
}
// invoked from UiSharedTopologyModel host listener
void moveHost(Host host, Host prevHost) {
UiHost uiHost = uiTopology.findHost(prevHost.id());
if (uiHost != null) {
updateHost(uiHost, host);
postEvent(HOST_MOVED, uiHost, MEMO_MOVED);
} else {
log.warn(E_NO_ELEMENT, "host", prevHost.id());
}
}
// package private for unit test access
UiHost accessHost(HostId id) {
return uiTopology.findHost(id);
}
// invoked from UiSharedTopologyModel host listener
void removeHost(Host host) {
HostId id = host.id();
UiHost uiHost = uiTopology.findHost(id);
if (uiHost != null) {
UiEdgeLink edgeLink = uiTopology.findEdgeLink(uiHost.edgeLinkId());
uiTopology.remove(edgeLink);
uiTopology.remove(uiHost);
postEvent(HOST_REMOVED, uiHost, MEMO_REMOVED);
} else {
log.warn(E_NO_ELEMENT, "host", id);
}
}
Set<UiHost> getAllHosts() {
return uiTopology.allHosts();
}
// === SYNTHETIC LINKS
List<UiSynthLink> getSynthLinks(RegionId regionId) {
return uiTopology.findSynthLinks(regionId);
}
Map<UiLinkId, UiSynthLink> relevantSynthLinks(RegionId regionId) {
Map<UiLinkId, UiSynthLink> result = new HashMap<>();
for (UiSynthLink sl : getSynthLinks(regionId)) {
result.put(sl.original().id(), sl);
}
return result;
}
/**
* Refreshes the internal state.
*/
public void refresh() {
// fix up internal linkages to ensure they are correct
// make sure regions reflect layout containment hierarchy
fixupContainmentHierarchy(uiTopology.nullRegion());
uiTopology.allRegions().forEach(this::fixupContainmentHierarchy);
// make sure devices and hosts are in the correct region
Set<UiDevice> allDevices = uiTopology.allDevices();
Set<UiHost> allHosts = uiTopology.allHosts();
services.region().getRegions().forEach(r -> {
RegionId rid = r.id();
UiRegion region = uiTopology.findRegion(rid);
if (region != null) {
reconcileDevicesAndHostsWithRegion(allDevices, allHosts, rid, region);
} else {
log.warn("No UiRegion in topology for ID {}", rid);
}
});
// what is left over, must belong to the null-region
Set<DeviceId> leftOver = new HashSet<>(allDevices.size());
allDevices.forEach(d -> leftOver.add(d.id()));
uiTopology.nullRegion().reconcileDevices(leftOver);
Set<HostId> leftOverHosts = new HashSet<>(allHosts.size());
allHosts.forEach(h -> leftOverHosts.add(h.id()));
uiTopology.nullRegion().reconcileHosts(leftOverHosts);
// now that we have correct region hierarchy, and devices are in their
// respective regions, we can compute synthetic links for each region.
uiTopology.computeSynthLinks();
}
private void reconcileDevicesAndHostsWithRegion(Set<UiDevice> allDevices,
Set<UiHost> allHosts,
RegionId rid,
UiRegion region) {
Set<DeviceId> deviceIds = services.region().getRegionDevices(rid);
Set<HostId> hostIds = new HashSet<>();
region.reconcileDevices(deviceIds);
deviceIds.forEach(devId -> {
UiDevice dev = uiTopology.findDevice(devId);
if (dev != null) {
dev.setRegionId(rid);
allDevices.remove(dev);
} else {
log.warn("Region device ID {} but no UiDevice in topology",
devId);
}
Set<Host> hosts = services.host().getConnectedHosts(devId);
for (Host h : hosts) {
HostId hid = h.id();
hostIds.add(hid);
UiHost host = uiTopology.findHost(hid);
if (host != null) {
host.setRegionId(rid);
allHosts.remove(host);
} else {
log.warn("Region host ID {} but no UiHost in topology",
hid);
}
}
});
region.reconcileHosts(hostIds);
}
// === CACHE STATISTICS
/**
* Returns a detailed (multi-line) string showing the contents of the cache.
*
* @return detailed string
*/
public String dumpString() {
return uiTopology.dumpString();
}
/**
* Returns the number of members in the cluster.
*
* @return number of cluster members
*/
public int clusterMemberCount() {
return uiTopology.clusterMemberCount();
}
/**
* Returns the number of regions in the topology.
*
* @return number of regions
*/
public int regionCount() {
return uiTopology.regionCount();
}
/**
* Returns the number of devices in the topology.
*
* @return number of devices
*/
public int deviceCount() {
return uiTopology.deviceCount();
}
/**
* Returns the number of device links in the topology.
*
* @return number of device links
*/
public int deviceLinkCount() {
return uiTopology.deviceLinkCount();
}
/**
* Returns the number of edge links in the topology.
*
* @return number of edge links
*/
public int edgeLinkCount() {
return uiTopology.edgeLinkCount();
}
/**
* Returns the number of hosts in the topology.
*
* @return number of hosts
*/
public int hostCount() {
return uiTopology.hostCount();
}
/**
* Returns the number of synthetic links in the topology.
*
* @return the number of synthetic links
*/
public int synthLinkCount() {
return uiTopology.synthLinkCount();
}
}