/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node; import static com.google.common.base.Preconditions.checkNotNull; import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.getOfPortNum; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchListener; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.Name; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.EncapsulationFormat; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayNodeConfig; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.ExternalInterfaces; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.Tunnel; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.TunnelBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeBase; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlan; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlanGpe; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; /** * Manage connected switches and ensure their configuration is set up * correctly */ public class SwitchManager implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(SwitchManager.class); protected static Map<NodeId, SwitchState> switches = new HashMap<>(); protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>(); private final FlowCapableNodeListener nodeListener; private final OfOverlayNodeListener ofOverlayNodeListener; private final FlowCapableNodeConnectorListener nodeConnectorListener; public SwitchManager(DataBroker dataProvider) { if (dataProvider == null) { LOG.warn("No data provider for {}. Listeners {}, {}, {} are not registered.", SwitchManager.class.getSimpleName(), FlowCapableNodeListener.class.getSimpleName(), OfOverlayNodeListener.class.getSimpleName(), FlowCapableNodeConnectorListener.class.getSimpleName()); nodeListener = null; ofOverlayNodeListener = null; nodeConnectorListener = null; } else { nodeListener = new FlowCapableNodeListener(dataProvider, this); ofOverlayNodeListener = new OfOverlayNodeListener(dataProvider, this); nodeConnectorListener = new FlowCapableNodeConnectorListener(dataProvider, this); } LOG.debug("Initialized OFOverlay switch manager"); } // When first endpoint is attached to switch, it can be ready public static void activatingSwitch(NodeId nodeId) { SwitchState state = switches.get(nodeId); if (state == null) { state = new SwitchState(nodeId); switches.put(nodeId, state); } state.setHasEndpoints(true); state.updateStatus(); } // When last endpoint is removed from switch, it is no longer ready public static void deactivatingSwitch(NodeId nodeId) { SwitchState state = switches.get(nodeId); if (state == null) { LOG.error("No SwitchState for {} in deactivatingSwitch. This should not happen.",nodeId); return; } state.setHasEndpoints(false); state.updateStatus(); } public synchronized InstanceIdentifier<NodeConnector> getNodeConnectorIidForPortName(Name portName) { for (SwitchState sw : switches.values()) { if (sw.fcncByNcIid == null) { continue; } for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : sw.fcncByNcIid .entrySet()) { FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue(); if (portName.getValue().equals(fcnc.getName())) { return fcncByNcIidEntry.getKey(); } } } return null; } /** * Get the collection of switches that are in the "ready" state. Note * that the collection is immutable. * * @return A {@link Collection} containing the switches that are ready. */ public synchronized Collection<NodeId> getReadySwitches() { ImmutableList<NodeId> readySwitches = FluentIterable.from(switches.values()) .filter(new Predicate<SwitchState>() { @Override public boolean apply(SwitchState input) { return input.status == SwitchStatus.READY; } }) .transform(new Function<SwitchState, NodeId>() { @Override public NodeId apply(SwitchState input) { return input.nodeId; } }) .toList(); LOG.trace("Get ready switches: {}", readySwitches); return readySwitches; } public synchronized Set<NodeConnectorId> getExternalPorts(NodeId nodeId) { SwitchState state = switches.get(nodeId); if (state == null) { return Collections.emptySet(); } return ImmutableSet.copyOf(state.externalPorts); } public Set<Long> getExternalPortNumbers(NodeId nodeId) { Set<Long> extPortNumbers = new HashSet<>(); for(NodeConnectorId nc : getExternalPorts(nodeId)) { long portNum; try { portNum = getOfPortNum(nc); } catch (NumberFormatException ex) { LOG.warn("Could not parse port number {}", nc, ex); return null; } extPortNumbers.add(portNum); } return extPortNumbers; } public synchronized Collection<NodeConnectorId> getTunnelPorts(NodeId nodeId) { Collection<NodeConnectorId> ncIds = new HashSet<>(); SwitchState state = switches.get(nodeId); if (state == null ) { return Collections.emptySet(); } ncIds = Collections2.transform(state.tunnelBuilderByType.values(),new Function<TunnelBuilder, NodeConnectorId>() { @Override public NodeConnectorId apply(TunnelBuilder input) { return input.getNodeConnectorId(); } }); return ncIds; } public synchronized NodeConnectorId getTunnelPort(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) { SwitchState state = switches.get(nodeId); if (state == null) { return null; } TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType); if (tunnel == null) { return null; } return tunnel.getNodeConnectorId(); } public synchronized IpAddress getTunnelIP(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) { SwitchState state = switches.get(nodeId); if (state == null) { return null; } TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType); if (tunnel == null) { return null; } return tunnel.getIp(); } /** * Add a {@link SwitchListener} to get notifications of switch events * * @param listener the {@link SwitchListener} to add */ public void registerListener(SwitchListener listener) { listeners.add(listener); } /** * Set the encapsulation format the specified value * * @param format The new format */ public void setEncapsulationFormat(EncapsulationFormat format) { // No-op for now } synchronized void updateSwitch(NodeId nodeId, @Nullable FlowCapableNode fcNode) { SwitchState state = getSwitchState(checkNotNull(nodeId)); SwitchStatus oldStatus = state.status; state.setFlowCapableNode(fcNode); handleSwitchState(state, oldStatus); } synchronized void updateSwitchNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid, @Nullable FlowCapableNodeConnector fcnc) { NodeId nodeId = ncIid.firstKeyOf(Node.class, NodeKey.class).getId(); SwitchState state = getSwitchState(nodeId); SwitchStatus oldStatus = state.status; state.setNodeConnectorConfig(ncIid, fcnc); handleSwitchState(state, oldStatus); } synchronized void updateSwitchConfig(NodeId nodeId, @Nullable OfOverlayNodeConfig config) { SwitchState state = getSwitchState(checkNotNull(nodeId)); SwitchStatus oldStatus = state.status; state.setConfig(config); handleSwitchState(state, oldStatus); } private SwitchState getSwitchState(NodeId id) { SwitchState state = switches.get(id); if (state == null) { state = new SwitchState(id); switches.put(id, state); LOG.trace("Switch {} added to switches {}", state.nodeId.getValue(), switches.keySet()); } return state; } private void handleSwitchState(SwitchState state, SwitchStatus oldStatus) { if (oldStatus == SwitchStatus.READY && state.status != SwitchStatus.READY) { LOG.info("Switch {} removed", state.nodeId.getValue()); notifySwitchRemoved(state.nodeId); } else if (oldStatus != SwitchStatus.READY && state.status == SwitchStatus.READY) { LOG.info("Switch {} ready", state.nodeId.getValue()); notifySwitchReady(state.nodeId); } else if (oldStatus == SwitchStatus.READY && state.status == SwitchStatus.READY) { // TODO Be msunal we could improve this by ignoring of updates where uninteresting fields are changed LOG.debug("Switch {} updated", state.nodeId.getValue()); notifySwitchUpdated(state.nodeId); } if (state.status == SwitchStatus.DISCONNECTED && state.isConfigurationEmpty()) { switches.remove(state.nodeId); LOG.trace("Switch {} removed from switches {}", state.nodeId, switches.keySet()); } } private void notifySwitchRemoved(NodeId nodeId) { for (SwitchListener listener : listeners) { listener.switchRemoved(nodeId); } } private void notifySwitchReady(NodeId nodeId) { for (SwitchListener listener : listeners) { listener.switchReady(nodeId); } } private void notifySwitchUpdated(NodeId nodeId) { for (SwitchListener listener : listeners) { listener.switchUpdated(nodeId); } } @Override public void close() throws Exception { nodeListener.close(); ofOverlayNodeListener.close(); nodeConnectorListener.close(); } /** * Internal representation of the state of a connected switch */ protected static final class SwitchState { private NodeId nodeId; private FlowCapableNode fcNode; private OfOverlayNodeConfig nodeConfig; private Map<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIid = Maps.newHashMap(); private boolean hasEndpoints=false; public boolean isHasEndpoints() { return hasEndpoints; } public void setHasEndpoints(boolean hasEndpoints) { this.hasEndpoints = hasEndpoints; } Map<Class<? extends TunnelTypeBase>, TunnelBuilder> tunnelBuilderByType = new HashMap<>(); Set<NodeConnectorId> externalPorts = new HashSet<>(); SwitchStatus status; public SwitchState(NodeId switchNode) { super(); nodeId = switchNode; } /** * Constructor used for tests * * @param node the node id * @param tunnelPort the tunnel port * @param externalPorts the set of expternal ports * @param nodeConfig the ofoverlay node config */ public SwitchState(NodeId node, NodeConnectorId tunnelPort, Set<NodeConnectorId> externalPorts, OfOverlayNodeConfig nodeConfig) { this.nodeId = node; this.nodeConfig = nodeConfig; update(); this.externalPorts = externalPorts; } private void update() { tunnelBuilderByType = new HashMap<>(); externalPorts = new HashSet<>(); if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null) { for (ExternalInterfaces nc : nodeConfig.getExternalInterfaces()) { externalPorts.add(nc.getNodeConnectorId()); } } if (nodeConfig != null && nodeConfig.getTunnel() != null) { for (Tunnel tunnel : nodeConfig.getTunnel()) { TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(tunnel.getTunnelType()); if (tunnelBuilder == null) { tunnelBuilder = new TunnelBuilder(); tunnelBuilderByType.put(tunnel.getTunnelType(), tunnelBuilder); } if (tunnel.getIp() != null) { tunnelBuilder.setIp(tunnel.getIp()); } if (tunnel.getNodeConnectorId() != null) { tunnelBuilder.setNodeConnectorId(tunnel.getNodeConnectorId()); } if (tunnel.getPort() != null) { tunnelBuilder.setPort(tunnel.getPort()); } } } for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : fcncByNcIid.entrySet()) { FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue(); if (fcnc.getName() == null) { continue; } InstanceIdentifier<NodeConnector> ncIid = fcncByNcIidEntry.getKey(); NodeConnectorId ncId = ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId(); if (fcnc.getName().matches(".*(vxlan-).*")) { TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(TunnelTypeVxlan.class); if (tunnelBuilder == null) { tunnelBuilder = new TunnelBuilder().setTunnelType(TunnelTypeVxlan.class); tunnelBuilderByType.put(TunnelTypeVxlan.class, tunnelBuilder); } tunnelBuilder.setNodeConnectorId(ncId); } else if (fcnc.getName().matches(".*(vxlangpe-).*")) { TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(TunnelTypeVxlanGpe.class); if (tunnelBuilder == null) { tunnelBuilder = new TunnelBuilder().setTunnelType(TunnelTypeVxlanGpe.class); tunnelBuilderByType.put(TunnelTypeVxlanGpe.class, tunnelBuilder); } tunnelBuilder.setNodeConnectorId(ncId); } } } private void updateStatus() { boolean tunnelWithIpAndNcExists = tunnelWithIpAndNcExists(); if (fcNode != null) { if (tunnelWithIpAndNcExists && isHasEndpoints()) { setStatus(SwitchStatus.READY); } else { setStatus(SwitchStatus.PREPARING); } } else { setStatus(SwitchStatus.DISCONNECTED); } } private void setStatus(SwitchStatus newStatus) { if (Objects.equal(status, newStatus)) { return; } LOG.debug("Switch {} is changing status from {} to {}", nodeId.getValue(), this.status, newStatus); this.status = newStatus; } private boolean tunnelWithIpAndNcExists() { if (tunnelBuilderByType.isEmpty()) { LOG.trace("No tunnel on switch {}", nodeId.getValue()); return false; } LOG.trace("Iterating over tunnel till tunnel with IP and node-connector is not found."); for (TunnelBuilder tb : tunnelBuilderByType.values()) { if (tb.getIp() != null && tb.getNodeConnectorId() != null) { LOG.trace("Tunnel {} found.",tb.toString()); return true; } else { LOG.trace("Tunnel is not complete for node: {}", nodeId.getValue()); } } return false; } public boolean isConfigurationEmpty() { if (fcNode != null) { return false; } if (nodeConfig != null) { return false; } if (!fcncByNcIid.isEmpty()) { return false; } return true; } public void setFlowCapableNode(FlowCapableNode fcNode) { this.fcNode = fcNode; LOG.trace("Switch {} set {}", nodeId.getValue(), fcNode); updateStatus(); } public void setConfig(OfOverlayNodeConfig config) { this.nodeConfig = config; LOG.trace("Switch {} set {}", nodeId.getValue(), config); update(); updateStatus(); } public void setNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid, FlowCapableNodeConnector fcnc) { if (fcnc == null) { fcncByNcIid.remove(ncIid); } else { fcncByNcIid.put(ncIid, fcnc); } LOG.trace("Switch {} node connector {} set {}", nodeId.getValue(), ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue(), fcnc); update(); updateStatus(); } } protected enum SwitchStatus { /** * The switch is not currently connected */ DISCONNECTED, /** * The switch is connected but not yet configured */ PREPARING, /** * The switch is ready to for policy rules to be installed */ READY } }