--- /dev/null
+
+/*
+ * Copyright (c) 2013 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.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.IReadFilterInternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IReadServiceFilter;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainerAware;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeTable;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+import org.opendaylight.controller.sal.reader.NodeTableStatistics;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.utils.NodeTableCreator;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.statistics.OFFlowStatisticsReply;
+import org.openflow.protocol.statistics.OFPortStatisticsReply;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.statistics.OFTableStatistics;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * Read Service shim layer which is in charge of filtering the flow statistics
+ * based on container. It is a Global instance.
+ */
+public class ReadServiceFilter implements IReadServiceFilter, IContainerListener, IOFStatisticsListener, IContainerAware {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ReadServiceFilter.class);
+ private IController controller = null;
+ private IOFStatisticsManager statsMgr = null;
+ private ConcurrentMap<String, Set<NodeConnector>> containerToNc;
+ private ConcurrentMap<String, Set<Node>> containerToNode;
+ private ConcurrentMap<String, Set<NodeTable>> containerToNt;
+ private ConcurrentMap<String, Set<ContainerFlow>> containerFlows;
+ private ConcurrentMap<String, IReadFilterInternalListener> readFilterInternalListeners =
+ new ConcurrentHashMap<String, IReadFilterInternalListener>();
+
+ public void setController(IController core) {
+ this.controller = core;
+ }
+
+ public void unsetController(IController core) {
+ if (this.controller == core) {
+ this.controller = null;
+ }
+ }
+
+ public void setReadFilterInternalListener(Map<?, ?> props, IReadFilterInternalListener s) {
+ if (props == null) {
+ logger.error("Failed setting Read Filter Listener, property map is null.");
+ return;
+ }
+ String containerName = (String) props.get("containerName");
+ if (containerName == null) {
+ logger.error("Failed setting Read Filter Listener, container name not supplied.");
+ return;
+ }
+ if ((this.readFilterInternalListeners != null) && !this.readFilterInternalListeners.containsValue(s)) {
+ this.readFilterInternalListeners.put(containerName, s);
+ logger.trace("Added Read Filter Listener for container {}", containerName);
+ }
+ }
+
+ public void unsetReadFilterInternalListener(Map<?, ?> props, IReadFilterInternalListener s) {
+ if (props == null) {
+ logger.error("Failed unsetting Read Filter Listener, property map is null.");
+ return;
+ }
+ String containerName = (String) props.get("containerName");
+ if (containerName == null) {
+ logger.error("Failed unsetting Read Filter Listener, containerName not supplied");
+ return;
+ }
+ if ((this.readFilterInternalListeners != null) && this.readFilterInternalListeners.get(containerName) != null
+ && this.readFilterInternalListeners.get(containerName).equals(s)) {
+ this.readFilterInternalListeners.remove(containerName);
+ logger.trace("Removed Read Filter Listener for container {}", containerName);
+ }
+ }
+
+ /**
+ * Function called by the dependency manager when all the required
+ * dependencies are satisfied
+ *
+ */
+ void init() {
+ containerToNc = new ConcurrentHashMap<String, Set<NodeConnector>>();
+ containerToNt = new ConcurrentHashMap<String, Set<NodeTable>>();
+ containerToNode = new ConcurrentHashMap<String, Set<Node>>();
+ containerFlows = new ConcurrentHashMap<String, Set<ContainerFlow>>();
+ }
+
+ /**
+ * Function called by the dependency manager when at least one
+ * dependency become unsatisfied or when the component is shutting
+ * down because for example bundle is being stopped.
+ *
+ */
+ void destroy() {
+ readFilterInternalListeners.clear();
+ }
+
+ /**
+ * Function called by dependency manager after "init ()" is called
+ * and after the services provided by the class are registered in
+ * the service registry
+ *
+ */
+ void start() {
+ }
+
+ /**
+ * Function called by the dependency manager before the services
+ * exported by the component are unregistered, this will be
+ * followed by a "destroy ()" calls
+ *
+ */
+ void stop() {
+ }
+
+ public void setService(IOFStatisticsManager service) {
+ this.statsMgr = service;
+ }
+
+ public void unsetService(IOFStatisticsManager service) {
+ this.statsMgr = null;
+ }
+
+ @Override
+ public FlowOnNode readFlow(String container, Node node, Flow flow, boolean cached) {
+
+ if (controller == null) {
+ // Avoid to provide cached statistics if controller went down.
+ // They are not valid anymore anyway
+ logger.error("Internal plugin error");
+ return null;
+ }
+
+ long sid = (Long) node.getID();
+ OFMatch ofMatch = new FlowConverter(flow).getOFMatch();
+ List<OFStatistics> ofList;
+ if (cached == true){
+ ofList = statsMgr.getOFFlowStatistics(sid, ofMatch, flow.getPriority());
+ } else {
+ ofList = statsMgr.queryStatistics(sid, OFStatisticsType.FLOW, ofMatch);
+ for (OFStatistics ofStat : ofList) {
+ if (((OFFlowStatisticsReply)ofStat).getPriority() == flow.getPriority()){
+ ofList = new ArrayList<OFStatistics>(1);
+ ofList.add(ofStat);
+ break;
+ }
+ }
+ }
+
+ // Convert and filter the statistics per container
+ List<FlowOnNode> flowOnNodeList = new FlowStatisticsConverter(ofList).getFlowOnNodeList(node);
+ List<FlowOnNode> filteredList = filterFlowListPerContainer(container, node, flowOnNodeList);
+
+ return (filteredList.isEmpty()) ? null : filteredList.get(0);
+ }
+
+ @Override
+ public List<FlowOnNode> readAllFlow(String container, Node node,
+ boolean cached) {
+
+ long sid = (Long) node.getID();
+ List<OFStatistics> ofList = (cached == true) ? statsMgr
+ .getOFFlowStatistics(sid) : statsMgr.queryStatistics(sid,
+ OFStatisticsType.FLOW, null);
+
+ // Convert and filter the statistics per container
+ List<FlowOnNode> flowOnNodeList = new FlowStatisticsConverter(ofList).getFlowOnNodeList(node);
+
+ return filterFlowListPerContainer(container, node, flowOnNodeList);
+ }
+
+ @Override
+ public NodeDescription readDescription(Node node, boolean cached) {
+
+ if (controller == null) {
+ logger.error("Internal plugin error");
+ return null;
+ }
+
+ long sid = (Long) node.getID();
+ List<OFStatistics> ofList = (cached == true) ? statsMgr
+ .getOFDescStatistics(sid) : statsMgr.queryStatistics(sid,
+ OFStatisticsType.DESC, null);
+
+ return new DescStatisticsConverter(ofList).getHwDescription();
+ }
+
+ /**
+ * Filters a list of FlowOnNode elements based on the container
+ *
+ * @param container
+ * @param nodeId
+ * @param list
+ * @return
+ */
+ private List<FlowOnNode> filterFlowListPerContainer(String container,
+ Node nodeId, List<FlowOnNode> list) {
+ if (list == null) {
+ return Collections.emptyList();
+ }
+
+ // Create new filtered list of flows
+ List<FlowOnNode> newList = new ArrayList<FlowOnNode>();
+
+ for (FlowOnNode target : list) {
+ // Check whether the described flow (match + actions) belongs to this container
+ if (flowBelongToContainer(container, nodeId, target.getFlow())) {
+ newList.add(target);
+ }
+ }
+
+ return newList;
+ }
+
+ /**
+ * Filters a list of OFStatistics elements based on the container
+ *
+ * @param container
+ * @param nodeId
+ * @param list
+ * @return
+ */
+ private List<OFStatistics> filterPortListPerContainer(String container, long switchId, List<OFStatistics> list) {
+ if (list == null) {
+ return Collections.emptyList();
+ }
+
+ // Create new filtered list of flows
+ List<OFStatistics> newList = new ArrayList<OFStatistics>();
+
+ for (OFStatistics stat : list) {
+ OFPortStatisticsReply target = (OFPortStatisticsReply) stat;
+ NodeConnector nc = NodeConnectorCreator.createOFNodeConnector(
+ target.getPortNumber(), NodeCreator.createOFNode(switchId));
+ if (containerOwnsNodeConnector(container, nc)) {
+ newList.add(target);
+ }
+ }
+
+ return newList;
+ }
+
+
+ private List<OFStatistics> filterTableListPerContainer(
+ String container, long switchId, List<OFStatistics> list) {
+ if (list == null) {
+ return Collections.emptyList();
+ }
+
+ // Create new filtered list of node tables
+ List<OFStatistics> newList = new ArrayList<OFStatistics>();
+
+ for (OFStatistics stat : list) {
+ OFTableStatistics target = (OFTableStatistics) stat;
+ NodeTable nt = NodeTableCreator.createOFNodeTable(target.getTableId(), NodeCreator.createOFNode(switchId));
+ if (containerOwnsNodeTable(container, nt)) {
+ newList.add(target);
+ }
+ }
+
+ return newList;
+ }
+
+ /**
+ * Returns whether the specified flow (flow match + actions)
+ * belongs to the container
+ *
+ * @param container
+ * @param node
+ * @param flow
+ * @return true if it belongs
+ */
+ public boolean flowBelongToContainer(String container, Node node, Flow flow) {
+ // All flows belong to the default container
+ if (container.equals(GlobalConstants.DEFAULT.toString())) {
+ return true;
+ }
+ return (flowPortsBelongToContainer(container, node, flow) &&
+ flowVlanBelongsToContainer(container, node, flow) &&
+ isFlowAllowedByContainer(container, flow));
+ }
+
+ /**
+ * Returns whether the passed NodeConnector belongs to the container
+ *
+ * @param container container name
+ * @param p node connector to test
+ * @return true if belongs false otherwise
+ */
+ public boolean containerOwnsNodeConnector(String container, NodeConnector p) {
+ // All node connectors belong to the default container
+ if (container.equals(GlobalConstants.DEFAULT.toString())) {
+ return true;
+ }
+ Set<NodeConnector> portSet = containerToNc.get(container);
+ return (portSet == null) ? false : portSet.contains(p);
+ }
+
+ /**
+ * Returns whether the passed NodeConnector belongs to the container
+ *
+ * @param container container name
+ * @param table node table to test
+ * @return true if belongs false otherwise
+ */
+ public boolean containerOwnsNodeTable(String container, NodeTable table) {
+ // All node table belong to the default container
+ if (container.equals(GlobalConstants.DEFAULT.toString())) {
+ return true;
+ }
+ Set<NodeTable> tableSet = containerToNt.get(container);
+ return (tableSet == null) ? false : tableSet.contains(table);
+ }
+
+ /**
+ * Returns whether the container flows allow the passed flow
+ *
+ * @param container
+ * @param match
+ * @return
+ */
+ private boolean isFlowAllowedByContainer(String container, Flow flow) {
+ Set<ContainerFlow> cFlowSet = this.containerFlows.get(container);
+ if (cFlowSet == null || cFlowSet.isEmpty()) {
+ return true;
+ }
+ for (ContainerFlow cFlow : cFlowSet) {
+ if (cFlow.allowsFlow(flow)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the vlan field in the flow match is the same
+ * of the static vlan configured for the container
+ *
+ * @param container
+ * @param node
+ * @param flow
+ * @return
+ */
+ private boolean flowVlanBelongsToContainer(String container, Node node, Flow flow) {
+ return true; // Always true for now
+ }
+
+ /**
+ * Check whether the ports in the flow match and flow actions for
+ * the specified node belong to the container
+ *
+ * @param container
+ * @param node
+ * @param flow
+ * @return
+ */
+ private boolean flowPortsBelongToContainer(String container, Node node,
+ Flow flow) {
+ Match m = flow.getMatch();
+ if (m.isPresent(MatchType.IN_PORT)) {
+ NodeConnector inPort = (NodeConnector) m.getField(MatchType.IN_PORT).getValue();
+ // If the incoming port is specified, check if it belongs to
+ if (!containerOwnsNodeConnector(container, inPort)) {
+ return false;
+ }
+ }
+
+ // If an outgoing port is specified, it must belong to this container
+ for (Action action : flow.getActions()) {
+ if (action.getType() == ActionType.OUTPUT) {
+ NodeConnector outPort = ((Output) action).getPort();
+ if (!containerOwnsNodeConnector(container, outPort)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void containerFlowUpdated(String containerName, ContainerFlow previousFlow,
+ ContainerFlow currentFlow, UpdateType t) {
+ Set<ContainerFlow> cFlowSet = containerFlows.get(containerName);
+ switch (t) {
+ case ADDED:
+ if (cFlowSet == null) {
+ cFlowSet = new HashSet<ContainerFlow>();
+ containerFlows.put(containerName, cFlowSet);
+ }
+ cFlowSet.add(currentFlow);
+ case CHANGED:
+ break;
+ case REMOVED:
+ if (cFlowSet != null) {
+ cFlowSet.remove(currentFlow);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void nodeConnectorUpdated(String containerName, NodeConnector p, UpdateType type) {
+
+ switch (type) {
+ case ADDED:
+ if (!containerToNc.containsKey(containerName)) {
+ containerToNc.put(containerName,
+ Collections.newSetFromMap(new ConcurrentHashMap<NodeConnector,Boolean>()));
+ }
+ containerToNc.get(containerName).add(p);
+ if (!containerToNode.containsKey(containerName)) {
+ containerToNode.put(containerName, new HashSet<Node>());
+ }
+ containerToNode.get(containerName).add(p.getNode());
+ break;
+ case REMOVED:
+ Set<NodeConnector> ncSet = containerToNc.get(containerName);
+ if (ncSet != null) {
+ //remove this nc from container map
+ ncSet.remove(p);
+
+ //check if there are still ports of this node in this container
+ //and if not, remove its mapping
+ boolean nodeInContainer = false;
+ Node node = p.getNode();
+ for (NodeConnector nodeConnector : ncSet) {
+ if (nodeConnector.getNode().equals(node)){
+ nodeInContainer = true;
+ break;
+ }
+ }
+ if (! nodeInContainer) {
+ Set<Node> nodeSet = containerToNode.get(containerName);
+ if (nodeSet != null) {
+ nodeSet.remove(node);
+ }
+ }
+ }
+ break;
+ case CHANGED:
+ default:
+ }
+ }
+
+ @Override
+ public void tagUpdated(String containerName, Node n, short oldTag, short newTag, UpdateType t) {
+ // Not interested in this event
+ }
+
+ @Override
+ public void containerModeUpdated(UpdateType t) {
+ // Not interested in this event
+ }
+
+ @Override
+ public NodeConnectorStatistics readNodeConnector(String containerName, NodeConnector connector, boolean cached) {
+ if (!containerOwnsNodeConnector(containerName, connector)) {
+ return null;
+ }
+ Node node = connector.getNode();
+ long sid = (Long) node.getID();
+ short portId = (Short) connector.getID();
+ List<OFStatistics> ofList = (cached == true) ? statsMgr
+ .getOFPortStatistics(sid, portId) : statsMgr.queryStatistics(
+ sid, OFStatisticsType.PORT, portId);
+
+ List<NodeConnectorStatistics> ncStatistics = new PortStatisticsConverter(sid, ofList)
+ .getNodeConnectorStatsList();
+ return (ncStatistics.isEmpty()) ? new NodeConnectorStatistics() : ncStatistics.get(0);
+ }
+
+ @Override
+ public List<NodeConnectorStatistics> readAllNodeConnector(String containerName, Node node, boolean cached) {
+
+ long sid = (Long) node.getID();
+ List<OFStatistics> ofList = (cached == true) ? statsMgr
+ .getOFPortStatistics(sid) : statsMgr.queryStatistics(sid,
+ OFStatisticsType.PORT, null);
+
+ List<OFStatistics> filteredList = filterPortListPerContainer(containerName, sid, ofList);
+
+ return new PortStatisticsConverter(sid, filteredList).getNodeConnectorStatsList();
+ }
+
+ @Override
+ public long getTransmitRate(String containerName, NodeConnector connector) {
+ if (!containerOwnsNodeConnector(containerName, connector)) {
+ return 0;
+ }
+
+ long switchId = (Long) connector.getNode().getID();
+ short port = (Short) connector.getID();
+
+ return statsMgr.getTransmitRate(switchId, port);
+ }
+
+ @Override
+ public NodeTableStatistics readNodeTable(String containerName,
+ NodeTable table, boolean cached) {
+ if (!containerOwnsNodeTable(containerName, table)) {
+ return null;
+ }
+ Node node = table.getNode();
+ long sid = (Long) node.getID();
+ Byte tableId = (Byte) table.getID();
+ List<OFStatistics> ofList = (cached == true) ? statsMgr.getOFTableStatistics(sid, tableId) :
+ statsMgr.queryStatistics(sid, OFStatisticsType.TABLE, tableId);
+
+ List<NodeTableStatistics> ntStatistics = new TableStatisticsConverter(sid, ofList).getNodeTableStatsList();
+
+ return (ntStatistics.isEmpty()) ? new NodeTableStatistics() : ntStatistics.get(0);
+ }
+
+ @Override
+ public List<NodeTableStatistics> readAllNodeTable(String containerName, Node node, boolean cached) {
+ long sid = (Long) node.getID();
+ List<OFStatistics> ofList = (cached == true) ?
+ statsMgr.getOFTableStatistics(sid) : statsMgr.queryStatistics(sid, OFStatisticsType.TABLE, null);
+
+ List<OFStatistics> filteredList = filterTableListPerContainer(containerName, sid, ofList);
+
+ return new TableStatisticsConverter(sid, filteredList).getNodeTableStatsList();
+ }
+
+ @Override
+ public void descriptionStatisticsRefreshed(Long switchId, List<OFStatistics> description) {
+ String container;
+ IReadFilterInternalListener listener;
+ Node node = NodeCreator.createOFNode(switchId);
+ NodeDescription nodeDescription = new DescStatisticsConverter(description).getHwDescription();
+ for (Map.Entry<String, IReadFilterInternalListener> l : readFilterInternalListeners.entrySet()) {
+ container = l.getKey();
+ listener = l.getValue();
+ if (container == GlobalConstants.DEFAULT.toString()
+ || (containerToNode.containsKey(container) && containerToNode.get(container).contains(node))) {
+ listener.nodeDescriptionStatisticsUpdated(node, nodeDescription);
+ }
+ }
+ }
+
+ @Override
+ public void flowStatisticsRefreshed(Long switchId, List<OFStatistics> flows) {
+ String container;
+ IReadFilterInternalListener listener;
+ Node node = NodeCreator.createOFNode(switchId);
+ for (Map.Entry<String, IReadFilterInternalListener> l : readFilterInternalListeners.entrySet()) {
+ container = l.getKey();
+ listener = l.getValue();
+
+ // Convert and filter the statistics per container
+ List<FlowOnNode> flowOnNodeList = new FlowStatisticsConverter(flows).getFlowOnNodeList(node);
+ flowOnNodeList = filterFlowListPerContainer(container, node, flowOnNodeList);
+
+ // notify listeners
+ listener.nodeFlowStatisticsUpdated(node, flowOnNodeList);
+ }
+ }
+
+ @Override
+ public void portStatisticsRefreshed(Long switchId, List<OFStatistics> ports) {
+ String container;
+ IReadFilterInternalListener listener;
+ Node node = NodeCreator.createOFNode(switchId);
+ for (Map.Entry<String, IReadFilterInternalListener> l : readFilterInternalListeners.entrySet()) {
+ container = l.getKey();
+ listener = l.getValue();
+
+ // Convert and filter the statistics per container
+ List<OFStatistics> filteredPorts = filterPortListPerContainer(container, switchId, ports);
+ List<NodeConnectorStatistics> ncStatsList = new PortStatisticsConverter(switchId, filteredPorts)
+ .getNodeConnectorStatsList();
+
+ // notify listeners
+ listener.nodeConnectorStatisticsUpdated(node, ncStatsList);
+ }
+ }
+
+ @Override
+ public void tableStatisticsRefreshed(Long switchId, List<OFStatistics> tables) {
+ String container;
+ Node node = NodeCreator.createOFNode(switchId);
+ for (Map.Entry<String, IReadFilterInternalListener> l : readFilterInternalListeners.entrySet()) {
+ container = l.getKey();
+
+ // Convert and filter the statistics per container
+ List<OFStatistics> filteredList = filterTableListPerContainer(container, switchId, tables);
+ List<NodeTableStatistics> tableStatsList = new TableStatisticsConverter(switchId, filteredList)
+ .getNodeTableStatsList();
+
+ // notify listeners
+ l.getValue().nodeTableStatisticsUpdated(node, tableStatsList);
+ }
+ }
+
+ @Override
+ public void containerCreate(String containerName) {
+ // do nothing
+ }
+
+ @Override
+ public void containerDestroy(String containerName) {
+ containerToNc.remove(containerName);
+ containerToNode.remove(containerName);
+ containerToNt.remove(containerName);
+ containerFlows.remove(containerName);
+ }
+}