package net.onrc.onos.core.topology; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IOF13Switch; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitch.PortChangeType; import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.util.SingletonTask; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.onrc.onos.api.batchoperation.BatchOperationEntry; import net.onrc.onos.core.configmanager.INetworkConfigService; import net.onrc.onos.core.configmanager.INetworkConfigService.LinkConfigStatus; import net.onrc.onos.core.configmanager.INetworkConfigService.NetworkConfigState; import net.onrc.onos.core.configmanager.INetworkConfigService.SwitchConfigStatus; import net.onrc.onos.core.datagrid.IDatagridService; import net.onrc.onos.core.datagrid.IEventChannel; import net.onrc.onos.core.hostmanager.Host; import net.onrc.onos.core.hostmanager.IHostListener; import net.onrc.onos.core.hostmanager.IHostService; import net.onrc.onos.core.linkdiscovery.ILinkDiscoveryListener; import net.onrc.onos.core.linkdiscovery.ILinkDiscoveryService; import net.onrc.onos.core.linkdiscovery.Link; import net.onrc.onos.core.registry.IControllerRegistryService; import net.onrc.onos.core.registry.IControllerRegistryService.ControlChangeCallback; import net.onrc.onos.core.registry.RegistryException; import net.onrc.onos.core.util.Dpid; import net.onrc.onos.core.util.LinkTuple; import net.onrc.onos.core.util.OnosInstanceId; import net.onrc.onos.core.util.PortNumberUtils; import net.onrc.onos.core.util.SwitchPort; import org.apache.commons.lang3.concurrent.ConcurrentUtils; import org.projectfloodlight.openflow.protocol.OFPortConfig; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFPortState; import org.projectfloodlight.openflow.util.HexString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class for publishing topology-related events. * * The events are received from the discovery modules, reformatted and * published to the other ONOS instances. * * TODO: Add a synchronization mechanism when publishing the events to * preserve the ordering and to avoid mismatch in the local "published" state, * because each of the caller (the discovery modules) might be running * on a different thread. */ public class TopologyPublisher implements IOFSwitchListener, ILinkDiscoveryListener, IFloodlightModule, IHostListener, ITopologyPublisherService { private static final Logger log = LoggerFactory.getLogger(TopologyPublisher.class); private IFloodlightProviderService floodlightProvider; private ILinkDiscoveryService linkDiscovery; private IControllerRegistryService registryService; private ITopologyService topologyService; private IDatagridService datagridService; private IHostService hostService; private MutableTopology mutableTopology; private INetworkConfigService networkConfigService; private static final String ENABLE_CLEANUP_PROPERTY = "EnableCleanup"; private boolean cleanupEnabled = true; private static final int CLEANUP_TASK_INTERVAL = 60; // in seconds private SingletonTask cleanupTask; private DelayedOperationsHandler delayedOperationsHandler = new DelayedOperationsHandler(); private IEventChannel<byte[], TopologyEvent> eventChannel; // // Local state for keeping track of locally published events so we can // cleanup properly when an entry is removed. // // We keep all Port, (incoming) Link and Host events per Switch DPID: // - If a switch goes down, we remove all corresponding Port, Link and // Host events. // - If a port on a switch goes down, we remove all corresponding Link // and Host events attached to this port. // // TODO: What to do if the Mastership changes? // - Cleanup state from publishedFoo maps, but do not send REMOVE events? // private ConcurrentMap<Dpid, MastershipData> publishedMastershipDataEntries = new ConcurrentHashMap<>(); private ConcurrentMap<Dpid, SwitchData> publishedSwitchDataEntries = new ConcurrentHashMap<>(); private ConcurrentMap<Dpid, ConcurrentMap<ByteBuffer, PortData>> publishedPortDataEntries = new ConcurrentHashMap<>(); private ConcurrentMap<Dpid, ConcurrentMap<ByteBuffer, LinkData>> publishedLinkDataEntries = new ConcurrentHashMap<>(); private ConcurrentMap<Dpid, ConcurrentMap<ByteBuffer, HostData>> publishedHostDataEntries = new ConcurrentHashMap<>(); private BlockingQueue<TopologyBatchOperation> delayedOperations = new LinkedBlockingQueue<>(); /** * Gets the ONOS Instance ID. * * @return the ONOS Instance ID. */ private OnosInstanceId getOnosInstanceId() { return registryService.getOnosInstanceId(); } /** * Cleanup old switches from the topology. Old switches are those which * have no controller in the registry. * * TODO: The overall switch cleanup mechanism needs refactoring/redesign. */ private class SwitchCleanup implements ControlChangeCallback, Runnable { @Override public void run() { String old = Thread.currentThread().getName(); Thread.currentThread().setName("SwitchCleanup@" + old); try { if (log.isTraceEnabled()) { log.trace("Running cleanup thread"); } switchCleanup(); } finally { cleanupTask.reschedule(CLEANUP_TASK_INTERVAL, TimeUnit.SECONDS); Thread.currentThread().setName(old); } } /** * First half of the switch cleanup operation. This method will attempt * to get control of any switch it sees without a controller via the * registry. */ private void switchCleanup() { Iterable<Switch> switches = mutableTopology.getSwitches(); if (log.isTraceEnabled()) { log.trace("Checking for inactive switches"); } // For each switch check if a controller exists in controller // registry for (Switch sw : switches) { // FIXME How to handle case where Switch has never been // registered to ZK if (sw.getConfigState() == ConfigState.CONFIGURED) { continue; } try { String controller = registryService.getControllerForSwitch(sw.getDpid().value()); if (controller == null) { log.debug("Requesting control to set switch {} INACTIVE", sw.getDpid()); registryService.requestControl(sw.getDpid().value(), this); } } catch (RegistryException e) { log.error("Caught RegistryException in cleanup thread", e); } } } /** * Second half of the switch cleanup operation. If the registry grants * control of a switch, we can be sure no other instance is writing * this switch to the topology, so we can remove it now. * * @param dpid the dpid of the switch we requested control for * @param hasControl whether we got control or not */ @Override public void controlChanged(long dpid, boolean hasControl) { if (hasControl) { log.debug("Got control to set switch {} INACTIVE", HexString.toHexString(dpid)); SwitchData switchData = new SwitchData(new Dpid(dpid)); publishRemoveSwitchEvent(switchData); registryService.releaseControl(dpid); } } } /** * A class to deal with Topology Operations that couldn't be pushed * to the Global Log writer, because they need to be delayed. * For example, a link cannot be pushed before the switches on both * ends are in the Global Log. * * TODO: This is an ugly hack that should go away: right now we have to * keep trying periodically. * TODO: Currently, we retry only ADD Link Events, everything else * is thrown away. */ private class DelayedOperationsHandler extends Thread { private static final long RETRY_INTERVAL_MS = 10; // 10ms @Override public void run() { List<TopologyBatchOperation> operations = new LinkedList<>(); this.setName("TopologyPublisher.DelayedOperationsHandler " + this.getId()); // // The main loop // while (true) { try { // // Block-waiting for an operation to be added, sleep // and try to publish it again. // TopologyBatchOperation firstTbo = delayedOperations.take(); Thread.sleep(RETRY_INTERVAL_MS); operations.add(firstTbo); delayedOperations.drainTo(operations); // Retry only the appropriate operations for (TopologyBatchOperation tbo : operations) { for (BatchOperationEntry< TopologyBatchOperation.Operator, TopologyEvent> boe : tbo.getOperations()) { TopologyBatchOperation.Operator oper = boe.getOperator(); switch (oper) { case ADD: TopologyEvent topologyEvent = boe.getTarget(); LinkData linkData = topologyEvent.getLinkData(); // // Test whether the Link Event still can be // published. // TODO: The implementation below has a bug: // If it happens that the same Link Event was // removed in the middle of checking, we might // incorrectly publish it again from here. // if (linkData == null) { break; } ConcurrentMap<ByteBuffer, LinkData> linkDataEntries = publishedLinkDataEntries.get( linkData.getDst().getDpid()); if (linkDataEntries == null) { break; } if (linkDataEntries.get(linkData.getIDasByteBuffer()) == null) { break; } publishAddLinkEvent(linkData); break; case REMOVE: break; default: log.error("Unknown Topology Batch Operation {}", oper); break; } } } } catch (InterruptedException exception) { log.debug("Exception processing delayed operations: ", exception); } } } } @Override public void linkAdded(Link link) { LinkTuple linkTuple = new LinkTuple( new SwitchPort(link.getSrc(), link.getSrcPort()), new SwitchPort(link.getDst(), link.getDstPort())); LinkConfigStatus ret = networkConfigService.checkLinkConfig(linkTuple); if (ret.getConfigState() == NetworkConfigState.DENY) { log.warn("Discovered {} denied by configuration. {} " + "Not allowing it to proceed.", link, ret.getMsg()); return; } LinkData linkData = new LinkData(linkTuple); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. linkData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); if (ret.getConfigState() == NetworkConfigState.ACCEPT_ADD) { Map<String, String> attr = ret.getLinkConfig().getPublishAttributes(); for (Entry<String, String> e : attr.entrySet()) { linkData.createStringAttribute(e.getKey(), e.getValue()); } linkData.createStringAttribute(TopologyElement.ELEMENT_CONFIG_STATE, ConfigState.CONFIGURED.toString()); } else { linkData.createStringAttribute(TopologyElement.ELEMENT_CONFIG_STATE, ConfigState.NOT_CONFIGURED.toString()); } linkData.createStringAttribute(TopologyElement.ELEMENT_ADMIN_STATUS, AdminStatus.ACTIVE.toString()); linkData.freeze(); publishAddLinkEvent(linkData); } @Override public void linkRemoved(Link link) { LinkData linkData = new LinkData( new SwitchPort(link.getSrc(), link.getSrcPort()), new SwitchPort(link.getDst(), link.getDstPort())); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. linkData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); linkData.freeze(); publishRemoveLinkEvent(linkData); } /* ***************** * IOFSwitchListener * *****************/ @Override public void switchActivatedMaster(long swId) { IOFSwitch sw = floodlightProvider.getSwitch(swId); final Dpid dpid = new Dpid(swId); if (sw == null) { log.warn("Added switch not available {} ", dpid); return; } SwitchConfigStatus ret = networkConfigService.checkSwitchConfig(dpid); if (ret.getConfigState() == NetworkConfigState.DENY) { log.warn("Activated switch {} denied by network configuration. {} " + "Not allowing it to proceed.", dpid, ret.getMsg()); return; } controllerRoleChanged(dpid, Role.MASTER); SwitchData switchData = new SwitchData(dpid); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. switchData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); switchData.createStringAttribute("ConnectedSince", sw.getConnectedSince().toString()); switchData.createStringAttribute(TopologyElement.ELEMENT_ADMIN_STATUS, AdminStatus.ACTIVE.toString()); // setting name from configuration (if it exists). if (ret.getSwitchConfig() != null) { switchData.createStringAttribute("name", ret.getSwitchConfig().getName()); } //set the switch socketAddress and port switchData.createStringAttribute("remoteAddress", sw.getChannelSocketAddress().toString().substring(1)); if (ret.getConfigState() == NetworkConfigState.ACCEPT_ADD) { Map<String, String> attr = ret.getSwitchConfig().getPublishAttributes(); for (Entry<String, String> e : attr.entrySet()) { switchData.createStringAttribute(e.getKey(), e.getValue()); } if (sw instanceof IOF13Switch) { Map<String, String> pa = ((IOF13Switch) sw).getPublishAttributes(); for (Entry<String, String> e : pa.entrySet()) { switchData.createStringAttribute(e.getKey(), e.getValue()); } } switchData.createStringAttribute(TopologyElement.ELEMENT_CONFIG_STATE, ConfigState.CONFIGURED.toString()); } else { switchData.createStringAttribute(TopologyElement.ELEMENT_CONFIG_STATE, ConfigState.NOT_CONFIGURED.toString()); } switchData.freeze(); // The Port events List<PortData> portDataEntries = new ArrayList<PortData>(); for (OFPortDesc port : sw.getPorts()) { PortData portData = new PortData(dpid, PortNumberUtils.openFlow(port)); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. //portData. if (isEnabled(port)) { portData.createStringAttribute("state", "ACTIVE"); } else { portData.createStringAttribute("state", "INACTIVE"); } portData.createStringAttribute("name", port.getName()); portData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); portData.createStringAttribute(TopologyElement.ELEMENT_CONFIG_STATE, ConfigState.NOT_CONFIGURED.toString()); portData.createStringAttribute(TopologyElement.ELEMENT_ADMIN_STATUS, AdminStatus.ACTIVE.toString()); portData.freeze(); portDataEntries.add(portData); } publishAddSwitchEvent(switchData, portDataEntries); } private boolean isEnabled(OFPortDesc p) { return (p != null && !p.getState().contains(OFPortState.LINK_DOWN) && !p.getState().contains(OFPortState.BLOCKED) && !p.getConfig().contains( OFPortConfig.PORT_DOWN)); } @Override public void switchActivatedEqual(long swId) { final Dpid dpid = new Dpid(swId); controllerRoleChanged(dpid, Role.EQUAL); } @Override public void switchMasterToEqual(long swId) { final Dpid dpid = new Dpid(swId); controllerRoleChanged(dpid, Role.EQUAL); } @Override public void switchEqualToMaster(long swId) { // for now treat as switchActivatedMaster switchActivatedMaster(swId); } @Override public void switchDisconnected(long swId) { final Dpid dpid = new Dpid(swId); log.debug("Local switch disconnected: dpid = {} role = {}", dpid); Role role = Role.SLAVE; // TODO: Should be Role.UNKNOWN MastershipData mastershipData = new MastershipData(dpid, getOnosInstanceId(), role); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. mastershipData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_ALL_LAYERS); mastershipData.freeze(); publishRemoveSwitchMastershipEvent(mastershipData); } @Override public void switchPortChanged(long swId, OFPortDesc port, PortChangeType changeType) { switch (changeType) { case ADD: switchPortAdded(swId, port); break; case DELETE: switchPortRemoved(swId, port); break; case UP: // NOTE: Currently, we treat Port UP/DOWN same as Port ADD/DELETE switchPortAdded(swId, port); break; case DOWN: // NOTE: Currently, we treat Port UP/DOWN same as Port ADD/DELETE switchPortRemoved(swId, port); break; case OTHER_UPDATE: default: // XXX S what is the right set of port change handlers? log.debug("Topology publisher does not handle these port updates: {}", changeType); } } /** * Prepares an event for adding a port on a switch. * * @param switchId the switch ID (DPID) * @param port the port to add */ private void switchPortAdded(long switchId, OFPortDesc port) { final Dpid dpid = new Dpid(switchId); final PortData portData = new PortData(dpid, PortNumberUtils.openFlow(port)); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. portData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); portData.createStringAttribute("name", port.getName()); portData.freeze(); publishAddPortEvent(portData); } /** * Prepares an event for removing a port on a switch. * * @param switchId the switch ID (DPID) * @param port the port to remove */ private void switchPortRemoved(long switchId, OFPortDesc port) { final Dpid dpid = new Dpid(switchId); final PortData portData = new PortData(dpid, PortNumberUtils.openFlow(port)); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. portData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER); portData.createStringAttribute("name", port.getName()); portData.freeze(); publishRemovePortEvent(portData); } @Override public String getName() { return "topologyPublisher"; } /* ***************** * IFloodlightModule * *****************/ @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { List<Class<? extends IFloodlightService>> services = new ArrayList<Class<? extends IFloodlightService>>(); services.add(ITopologyPublisherService.class); return services; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> impls = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); impls.put(ITopologyPublisherService.class, this); return impls; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(ILinkDiscoveryService.class); l.add(IThreadPoolService.class); l.add(IControllerRegistryService.class); l.add(IDatagridService.class); l.add(ITopologyService.class); l.add(IHostService.class); l.add(INetworkConfigService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); linkDiscovery = context.getServiceImpl(ILinkDiscoveryService.class); registryService = context.getServiceImpl(IControllerRegistryService.class); datagridService = context.getServiceImpl(IDatagridService.class); hostService = context.getServiceImpl(IHostService.class); networkConfigService = context.getServiceImpl(INetworkConfigService.class); topologyService = context.getServiceImpl(ITopologyService.class); } @Override public void startUp(FloodlightModuleContext context) { floodlightProvider.addOFSwitchListener(this); linkDiscovery.addListener(this); hostService.addHostListener(this); eventChannel = datagridService.createChannel( TopologyManager.EVENT_CHANNEL_NAME, byte[].class, TopologyEvent.class); mutableTopology = topologyService.getTopology(); // Run the cleanup thread String enableCleanup = context.getConfigParams(this).get(ENABLE_CLEANUP_PROPERTY); if (enableCleanup != null && enableCleanup.equalsIgnoreCase("false")) { cleanupEnabled = false; } log.debug("Cleanup thread is {}enabled", (cleanupEnabled) ? "" : "not "); if (cleanupEnabled) { IThreadPoolService threadPool = context.getServiceImpl(IThreadPoolService.class); cleanupTask = new SingletonTask(threadPool.getScheduledExecutor(), new SwitchCleanup()); // Run the cleanup task immediately on startup cleanupTask.reschedule(0, TimeUnit.SECONDS); } // Run the Delayed Operations Handler thread delayedOperationsHandler.start(); } @Override public void hostAdded(Host host) { log.debug("Host added with MAC {}", host.getMacAddress()); SwitchPort sp = new SwitchPort(host.getSwitchDPID(), host.getSwitchPort()); List<SwitchPort> spLists = new ArrayList<SwitchPort>(); spLists.add(sp); HostData hostData = new HostData(host.getMacAddress(), host.getIpAddress()); hostData.setAttachmentPoints(spLists); hostData.setLastSeenTime(host.getLastSeenTimestamp().getTime()); // Does not use vlan info now. hostData.freeze(); publishAddHostEvent(hostData); } @Override public void hostRemoved(Host host) { log.debug("Host removed with MAC {}", host.getMacAddress()); // // Remove all previously added HostData for this MAC address // // TODO: Currently, the caller of hostRemoved() might not include // the correct set of Attachment Points in the HostData entry itself. // Also, we might have multiple HostData entries for the same // host (MAC address), each containing a single (different) Attachment // Point. // Hence, here we have to cleanup all HostData entries for this // particular host, based on its MAC address. // List<HostData> removeHostDataEntries = new LinkedList<>(); for (ConcurrentMap<ByteBuffer, HostData> cm : publishedHostDataEntries.values()) { for (HostData hostData : cm.values()) { if (hostData.getMac().equals(host.getMacAddress())) { removeHostDataEntries.add(hostData); } } } for (HostData hostData : removeHostDataEntries) { publishRemoveHostEvent(hostData); } } @Override public boolean publish(TopologyBatchOperation tbo) { publishTopologyOperations(tbo); return true; } /** * Prepares the Controller role changed event for a switch. * * @param dpid the switch DPID * @param role the new role of the controller */ private void controllerRoleChanged(Dpid dpid, Role role) { log.debug("Local switch controller mastership role changed: dpid = {} role = {}", dpid, role); MastershipData mastershipData = new MastershipData(dpid, getOnosInstanceId(), role); // FIXME should be merging, with existing attrs, etc.. // TODO define attr name as constant somewhere. // TODO populate appropriate attributes. mastershipData.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_ALL_LAYERS); mastershipData.freeze(); publishAddSwitchMastershipEvent(mastershipData); } /** * Publishes ADD Mastership Event. * * @param mastershipData the mastership event to publish */ private void publishAddSwitchMastershipEvent( MastershipData mastershipData) { // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(mastershipData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); publishTopologyOperations(tbo); publishedMastershipDataEntries.put(mastershipData.getDpid(), mastershipData); } /** * Publishes REMOVE Mastership Event. * * @param mastershipData the mastership event to publish */ private void publishRemoveSwitchMastershipEvent( MastershipData mastershipData) { if (publishedMastershipDataEntries.get(mastershipData.getDpid()) == null) { return; // Nothing to do } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(mastershipData, getOnosInstanceId()); tbo.appendRemoveOperation(topologyEvent); publishTopologyOperations(tbo); publishedMastershipDataEntries.remove(mastershipData.getDpid()); } /** * Publishes ADD Switch Event. * * @param switchData the switch event to publish * @param portDataEntries the corresponding port events for the switch to * publish */ private void publishAddSwitchEvent(SwitchData switchData, Collection<PortData> portDataEntries) { if (!registryService.hasControl(switchData.getOriginDpid().value())) { log.debug("Not the master for switch {}. Suppressed switch add event {}.", switchData.getOriginDpid(), switchData); return; } // Keep track of the old Port Events that should be removed ConcurrentMap<ByteBuffer, PortData> oldPortDataEntries = publishedPortDataEntries.get(switchData.getDpid()); if (oldPortDataEntries == null) { oldPortDataEntries = new ConcurrentHashMap<>(); } // Publish the information for the switch TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(switchData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); // Publish the information for each port ConcurrentMap<ByteBuffer, PortData> newPortDataEntries = new ConcurrentHashMap<>(); for (PortData portData : portDataEntries) { topologyEvent = new TopologyEvent(portData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); ByteBuffer id = portData.getIDasByteBuffer(); newPortDataEntries.put(id, portData); oldPortDataEntries.remove(id); } publishTopologyOperations(tbo); publishedSwitchDataEntries.put(switchData.getDpid(), switchData); publishedPortDataEntries.put(switchData.getDpid(), newPortDataEntries); // Cleanup for each of the old removed port for (PortData portData : oldPortDataEntries.values()) { publishRemovePortEvent(portData); } } /** * Publishes REMOVE Switch Event. * * @param switchData the switch event to publish */ private void publishRemoveSwitchEvent(SwitchData switchData) { // // TODO: Removed the check for now, because currently this method is // also called by the SwitchCleanup thread, and in that case // the Switch Event was published by some other ONOS instance. // /* if (publishedSwitchDataEntries.get(switchData.getDpid()) == null) { return; // Nothing to do } */ // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(switchData, getOnosInstanceId()); tbo.appendRemoveOperation(topologyEvent); publishTopologyOperations(tbo); publishedSwitchDataEntries.remove(switchData.getDpid()); // Cleanup for each port ConcurrentMap<ByteBuffer, PortData> portDataEntries = publishedPortDataEntries.get(switchData.getDpid()); if (portDataEntries != null) { for (PortData portData : portDataEntries.values()) { publishRemovePortEvent(portData); } } publishedPortDataEntries.remove(switchData.getDpid()); publishedLinkDataEntries.remove(switchData.getDpid()); publishedHostDataEntries.remove(switchData.getDpid()); } /** * Publishes ADD Port Event. * * @param portData the port event to publish */ private void publishAddPortEvent(PortData portData) { if (!registryService.hasControl(portData.getOriginDpid().value())) { log.debug("Not the master for switch {}. Suppressed port add event {}.", portData.getOriginDpid(), portData); return; } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(portData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); publishTopologyOperations(tbo); // Store the new Port Event in the local cache ConcurrentMap<ByteBuffer, PortData> portDataEntries = ConcurrentUtils.putIfAbsent(publishedPortDataEntries, portData.getDpid(), new ConcurrentHashMap<ByteBuffer, PortData>()); portDataEntries.put(portData.getIDasByteBuffer(), portData); } /** * Publishes REMOVE Port Event. * * @param portData the port event to publish */ private void publishRemovePortEvent(PortData portData) { ConcurrentMap<ByteBuffer, PortData> portDataEntries = publishedPortDataEntries.get(portData.getDpid()); if (portDataEntries == null) { return; // Nothing to do } if (portDataEntries.get(portData.getIDasByteBuffer()) == null) { return; // Nothing to do } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(portData, getOnosInstanceId()); tbo.appendRemoveOperation(topologyEvent); publishTopologyOperations(tbo); // Cleanup for the incoming link(s) ConcurrentMap<ByteBuffer, LinkData> linkDataEntries = publishedLinkDataEntries.get(portData.getDpid()); if (linkDataEntries != null) { for (LinkData linkData : linkDataEntries.values()) { if (linkData.getDst().equals(portData.getSwitchPort())) { publishRemoveLinkEvent(linkData); } } } // Cleanup for the connected hosts ConcurrentMap<ByteBuffer, HostData> hostDataEntries = publishedHostDataEntries.get(portData.getDpid()); if (hostDataEntries != null) { for (HostData hostData : hostDataEntries.values()) { for (SwitchPort swp : hostData.getAttachmentPoints()) { if (swp.equals(portData.getSwitchPort())) { publishRemoveHostEvent(hostData); } } } } portDataEntries.remove(portData.getIDasByteBuffer()); } /** * Publishes ADD Link Event. * * @param linkData the link event to publish */ private void publishAddLinkEvent(LinkData linkData) { if (!registryService.hasControl(linkData.getOriginDpid().value())) { log.debug("Not the master for dst switch {}. Suppressed link add event {}.", linkData.getOriginDpid(), linkData); return; } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(linkData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); publishTopologyOperations(tbo); // Store the new Link Event in the local cache ConcurrentMap<ByteBuffer, LinkData> linkDataEntries = ConcurrentUtils.putIfAbsent(publishedLinkDataEntries, linkData.getDst().getDpid(), new ConcurrentHashMap<ByteBuffer, LinkData>()); linkDataEntries.put(linkData.getIDasByteBuffer(), linkData); } /** * Publishes REMOVE Link Event. * * @param linkData the link event to publish */ private void publishRemoveLinkEvent(LinkData linkData) { ConcurrentMap<ByteBuffer, LinkData> linkDataEntries = publishedLinkDataEntries.get(linkData.getDst().getDpid()); if (linkDataEntries == null) { return; // Nothing to do } if (linkDataEntries.get(linkData.getIDasByteBuffer()) == null) { return; // Nothing to do } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(linkData, getOnosInstanceId()); tbo.appendRemoveOperation(topologyEvent); publishTopologyOperations(tbo); linkDataEntries.remove(linkData.getIDasByteBuffer()); } /** * Publishes ADD Host Event. * * @param hostData the host event to publish */ private void publishAddHostEvent(HostData hostData) { // // NOTE: The implementation below assumes that there is just one // attachment point stored in hostData. Currently, this assumption // is true based on the existing implementation of the caller // hostAdded(). // if (!registryService.hasControl(hostData.getOriginDpid().value())) { log.debug("Not the master for attachment switch {}. Suppressed host add event {}.", hostData.getOriginDpid(), hostData); return; } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(hostData, getOnosInstanceId()); tbo.appendAddOperation(topologyEvent); publishTopologyOperations(tbo); // Store the new Host Event in the local cache ConcurrentMap<ByteBuffer, HostData> hostDataEntries = ConcurrentUtils.putIfAbsent(publishedHostDataEntries, hostData.getOriginDpid(), new ConcurrentHashMap<ByteBuffer, HostData>()); hostDataEntries.put(hostData.getIDasByteBuffer(), hostData); } /** * Publishes REMOVE Host Event. * * @param hostData the host event to publish */ private void publishRemoveHostEvent(HostData hostData) { ConcurrentMap<ByteBuffer, HostData> hostDataEntries = publishedHostDataEntries.get(hostData.getOriginDpid()); if (hostDataEntries == null) { return; // Nothing to do } if (hostDataEntries.get(hostData.getIDasByteBuffer()) == null) { return; // Nothing to do } // Publish the information TopologyBatchOperation tbo = new TopologyBatchOperation(); TopologyEvent topologyEvent = new TopologyEvent(hostData, getOnosInstanceId()); tbo.appendRemoveOperation(topologyEvent); publishTopologyOperations(tbo); hostDataEntries.remove(hostData.getIDasByteBuffer()); } /** * Publishes Topology Operations. * * @param tbo the Topology Operations to publish */ private void publishTopologyOperations(TopologyBatchOperation tbo) { // TODO: This flag should be configurable boolean isGlobalLogWriter = false; log.debug("Publishing: {}", tbo); if (isGlobalLogWriter) { if (!topologyService.publish(tbo)) { log.debug("Cannot publish: {}", tbo); delayedOperations.add(tbo); } } else { // TODO: For now we publish each TopologyEvent independently for (BatchOperationEntry<TopologyBatchOperation.Operator, TopologyEvent> boe : tbo.getOperations()) { TopologyBatchOperation.Operator oper = boe.getOperator(); TopologyEvent topologyEvent = boe.getTarget(); switch (oper) { case ADD: eventChannel.addEntry(topologyEvent.getID(), topologyEvent); break; case REMOVE: eventChannel.removeEntry(topologyEvent.getID()); break; default: log.error("Unknown Topology Batch Operation {}", oper); break; } } } } }