From: Ed Warnicke Date: Mon, 26 Aug 2013 19:19:18 +0000 (+0000) Subject: Merge "Integration of MDSAL into distribution" X-Git-Tag: releasepom-0.1.0~173 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=68ef34f0d8f426ce525ccefb4e5a36162f2eb717;hp=44638c17493281db45754881afb491a620481fe3 Merge "Integration of MDSAL into distribution" --- diff --git a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/Activator.java b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/Activator.java index 4bee253765..c0d1b50a46 100644 --- a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/Activator.java +++ b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/Activator.java @@ -25,6 +25,7 @@ import org.apache.felix.dm.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase; +import org.opendaylight.controller.sal.inventory.IInventoryService; import org.opendaylight.controller.sal.inventory.IListenInventoryUpdates; public class Activator extends ComponentActivatorAbstractBase { @@ -37,6 +38,7 @@ public class Activator extends ComponentActivatorAbstractBase { * ComponentActivatorAbstractBase. * */ + @Override public void init() { } @@ -45,6 +47,7 @@ public class Activator extends ComponentActivatorAbstractBase { * cleanup done by ComponentActivatorAbstractBase * */ + @Override public void destroy() { } @@ -61,6 +64,7 @@ public class Activator extends ComponentActivatorAbstractBase { * @return The list of implementations the bundle will support, * in Global version */ + @Override protected Object[] getGlobalImplementations() { Object[] res = { ConnectionManager.class }; return res; @@ -74,6 +78,7 @@ public class Activator extends ComponentActivatorAbstractBase { * @param imp implementation to be configured * @param containerName container on which the configuration happens */ + @Override protected void configureGlobalInstance(Component c, Object imp) { if (imp.equals(ConnectionManager.class)) { Dictionary props = new Hashtable(); @@ -100,6 +105,9 @@ public class Activator extends ComponentActivatorAbstractBase { c.add(createServiceDependency().setService(IConnectionService.class) .setCallbacks("setConnectionService", "unsetConnectionService") .setRequired(true)); + c.add(createServiceDependency().setService(IInventoryService.class, "(scope=Global)") + .setCallbacks("setInventoryService", "unsetInventoryService") + .setRequired(true)); } } } diff --git a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/ConnectionManager.java b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/ConnectionManager.java index fdba533b5b..e2d8f6b03b 100644 --- a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/ConnectionManager.java +++ b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/internal/ConnectionManager.java @@ -32,7 +32,6 @@ import java.util.concurrent.LinkedBlockingQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.eclipse.osgi.framework.console.CommandInterpreter; import org.eclipse.osgi.framework.console.CommandProvider; import org.opendaylight.controller.clustering.services.ICacheUpdateAware; @@ -49,6 +48,7 @@ import org.opendaylight.controller.sal.core.Node; import org.opendaylight.controller.sal.core.NodeConnector; import org.opendaylight.controller.sal.core.Property; import org.opendaylight.controller.sal.core.UpdateType; +import org.opendaylight.controller.sal.inventory.IInventoryService; import org.opendaylight.controller.sal.inventory.IListenInventoryUpdates; import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.sal.utils.StatusCode; @@ -66,6 +66,7 @@ public class ConnectionManager implements IConnectionManager, IConnectionListene private IConnectionService connectionService; private Thread connectionEventThread; private BlockingQueue connectionEvents; + private IInventoryService inventoryService; public void setClusterServices(IClusterGlobalServices i) { this.clusterServices = i; @@ -87,12 +88,48 @@ public class ConnectionManager implements IConnectionManager, IConnectionListene } } + public void setInventoryService(IInventoryService service) { + logger.trace("Got inventory service set request {}", service); + this.inventoryService = service; + } + + public void unsetInventoryService(IInventoryService service) { + logger.trace("Got a service UNset request"); + this.inventoryService = null; + } + + private void getInventories() { + Map> nodeProp = this.inventoryService.getNodeProps(); + for (Map.Entry> entry : nodeProp.entrySet()) { + Node node = entry.getKey(); + logger.debug("getInventories for node:{}", new Object[] { node }); + Map propMap = entry.getValue(); + Set props = new HashSet(); + for (Property property : propMap.values()) { + props.add(property); + } + updateNode(node, UpdateType.ADDED, props); + } + + Map> nodeConnectorProp = this.inventoryService.getNodeConnectorProps(); + for (Map.Entry> entry : nodeConnectorProp.entrySet()) { + Map propMap = entry.getValue(); + Set props = new HashSet(); + for (Property property : propMap.values()) { + props.add(property); + } + updateNodeConnector(entry.getKey(), UpdateType.ADDED, props); + } + } + public void started() { connectionEventThread = new Thread(new EventHandler(), "ConnectionEvent Thread"); connectionEventThread.start(); registerWithOSGIConsole(); notifyClusterViewChanged(); + // Should pull the Inventory updates in case we missed it + getInventories(); } public void init() { diff --git a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/scheme/AbstractScheme.java b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/scheme/AbstractScheme.java index d93f74b4fe..d7c4e5f933 100644 --- a/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/scheme/AbstractScheme.java +++ b/opendaylight/connectionmanager/implementation/src/main/java/org/opendaylight/controller/connectionmanager/scheme/AbstractScheme.java @@ -237,13 +237,12 @@ public abstract class AbstractScheme { * with this controller to take hold of a Node. */ if (isConnectionAllowed(node)) { - if (!nodeConnections.replace(node, oldControllers, newControllers)) { + if (oldControllers == null || !nodeConnections.replace(node, oldControllers, newControllers)) { clusterServices.trollback(); try { Thread.sleep(100); } catch ( InterruptedException e) {} - log.debug("Replace failed... old={} with new={} for {} to {}", oldControllers.toString(), newControllers.toString(), - controller.getHostAddress(), node.toString()); + log.debug("Retrying ... {} with {}", controller.getHostAddress(), node.toString()); return putNodeToController(node, controller); } else { log.debug("Replace successful old={} with new={} for {} to {}", oldControllers.toString(), newControllers.toString(), diff --git a/opendaylight/distribution/opendaylight/src/main/resources/run.sh b/opendaylight/distribution/opendaylight/src/main/resources/run.sh index 313b3b2c79..2587a5d2ab 100755 --- a/opendaylight/distribution/opendaylight/src/main/resources/run.sh +++ b/opendaylight/distribution/opendaylight/src/main/resources/run.sh @@ -1,9 +1,5 @@ #!/bin/bash -[[ -z ${JAVA_HOME} ]] && echo "Need to set JAVA_HOME environment variable" && exit -1; -[[ ! -x ${JAVA_HOME}/bin/java ]] && echo "Cannot find an executable \ -JVM at path ${JAVA_HOME}/bin/java check your JAVA_HOME" && exit -1; - platform='unknown' unamestr=`uname` if [[ "$unamestr" == 'Linux' ]]; then @@ -32,8 +28,15 @@ elif [[ $platform == 'osx' ]]; then PHYS_DIR=`pwd -P` RESULT=$PHYS_DIR/$TARGET_FILE fullpath=$RESULT + + [[ -z ${JAVA_HOME} ]] && [[ -x "/usr/libexec/java_home" ]] && export JAVA_HOME=`/usr/libexec/java_home -v 1.7`; + fi +[[ -z ${JAVA_HOME} ]] && echo "Need to set JAVA_HOME environment variable" && exit -1; +[[ ! -x ${JAVA_HOME}/bin/java ]] && echo "Cannot find an executable \ +JVM at path ${JAVA_HOME}/bin/java check your JAVA_HOME" && exit -1; + basedir=`dirname ${fullpath}` function usage { diff --git a/opendaylight/forwardingrulesmanager/api/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java b/opendaylight/forwardingrulesmanager/api/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java index e8c5f648fa..83106a391c 100644 --- a/opendaylight/forwardingrulesmanager/api/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java +++ b/opendaylight/forwardingrulesmanager/api/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java @@ -168,14 +168,13 @@ public class FlowEntry implements Cloneable, Serializable { public FlowEntry mergeWith(ContainerFlow containerFlow) { Match myMatch = flow.getMatch(); - // Based on this flow direction, rearrange the match - Match match = containerFlow.getMatch(); + Match filter = containerFlow.getMatch(); // Merge - myMatch.mergeWithFilter(match); + Match merge = myMatch.mergeWithFilter(filter); // Replace this Flow's match with merged version - flow.setMatch(myMatch); + flow.setMatch(merge); return this; } diff --git a/opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java b/opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java index ad694b0b65..76974d7541 100644 --- a/opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java +++ b/opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java @@ -1127,11 +1127,11 @@ public class NorthboundIT { .append("dstNodeConnector", nodeConnectorType_2 + "|" + nodeConnectorId_2 + "@" + nodeType_2 + "|" + nodeId_2); // execute HTTP request and verify response code - result = getJsonResult(baseURL + "/userLink", "POST", jo.toString()); + result = getJsonResult(baseURL + "/user-link", "POST", jo.toString()); Assert.assertTrue(httpResponseCode == 201); // === test GET method for getUserLinks() - result = getJsonResult(baseURL + "/userLink", "GET"); + result = getJsonResult(baseURL + "/user-link", "GET"); Assert.assertTrue(httpResponseCode == 200); if (debugMsg) { System.out.println("result:" + result); @@ -1176,12 +1176,12 @@ public class NorthboundIT { // === test DELETE method for deleteUserLink() String userName = "userLink_1"; - result = getJsonResult(baseURL + "/userLink/" + userName, "DELETE"); + result = getJsonResult(baseURL + "/user-link/" + userName, "DELETE"); Assert.assertTrue(httpResponseCode == 200); // execute another getUserLinks() request to verify that userLink_1 is // removed - result = getJsonResult(baseURL + "/userLink", "GET"); + result = getJsonResult(baseURL + "/user-link", "GET"); Assert.assertTrue(httpResponseCode == 200); if (debugMsg) { System.out.println("result:" + result); diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java index 4a31317d95..32e1002c94 100644 --- a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java +++ b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java @@ -15,7 +15,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -@XmlRootElement +@XmlRootElement(name="list") @XmlAccessorType(XmlAccessType.NONE) public class NodeConnectors { diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java index b7f1c53ae8..061e51732e 100644 --- a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java +++ b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java @@ -15,7 +15,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -@XmlRootElement +@XmlRootElement(name="list") @XmlAccessorType(XmlAccessType.NONE) public class Nodes { diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java index e67d1b7568..98aa5ad94e 100644 --- a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java +++ b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java @@ -107,11 +107,56 @@ public class SwitchNorthbound { * Retrieve a list of all the nodes and their properties in the network * * @param containerName - * The container for which we want to retrieve the list + * Name of the Container (Eg. 'default') * @return A list of Pair each pair represents a * {@link org.opendaylight.controller.sal.core.Node} and Set of * {@link org.opendaylight.controller.sal.core.Property} attached to * it. + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/nodes
+     *
+     * Response in XML:
+     * <list>
+     *        <nodeProperties>
+     *               <node type="OF" id="00:00:00:00:00:00:00:02"/>
+     *               <properties>
+     *                      <tables>
+     *                             <value>-1</value>
+     *                      </tables>
+     *                      <description>
+     *                             <value>Switch2</value>
+     *                      </description>
+     *                      <actions>
+     *                             <value>4095</value>
+     *                      </actions>
+     *                      <macAddress>
+     *                             <value>00:00:00:00:00:02</value>
+     *                      </macAddress>
+     *                      <capabilities>
+     *                             <value>199</value>
+     *                      </capabilities>
+     *                      <timeStamp>
+     *                             <value>1377291227877</value>
+     *                             <name>connectedSince</name>
+     *                      </timeStamp>
+     *                      <buffers>
+     *                             <value>256</value>
+     *                      </buffers>
+     *               </properties>
+     *        </nodeProperties>
+     * </list>
+     *
+     * Response in JSON:
+     * {"nodeProperties":[{"node":{"@type":"OF","@id":"00:00:00:00:00:00:00:02"},"properties":{"tables":{"value":"-1"},
+     * "description":{"value":"None"},"actions":{"value":"4095"},"macAddress":{"value":"00:00:00:00:00:02"},"capabilities"
+     * :{"value":"199"},"timeStamp":{"value":"1377291227877","name":"connectedSince"},"buffers":{"value":"256"}}}]}
+     *
+     * 
*/ @Path("/{containerName}/nodes") @GET @@ -119,6 +164,7 @@ public class SwitchNorthbound { @TypeHint(Nodes.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public Nodes getNodes(@PathParam("containerName") String containerName) { @@ -161,49 +207,54 @@ public class SwitchNorthbound { } /** - * Add a Name, Tier and Forwarding mode property to a node. - * - *
-     * Example Request:
-     *  http://localhost:8080/controller/nb/v2/switch/red/node/OF/00:00:00:00:00:03/property/description/Switch3
-     *  (Valid properties that can be configured are: description, forwarding(only for default container) and tier)
-     * 
+ * Add a Description, Tier and Forwarding mode property to a node. * * @param containerName - * Name of the Container + * Name of the Container (Eg. 'default') * @param nodeType - * Type of the node being programmed + * Type of the node being programmed (Eg. 'OF') * @param nodeId * Node Identifier as specified by * {@link org.opendaylight.controller.sal.core.Node} - * @param propName - * Name of the Property specified by - * {@link org.opendaylight.controller.sal.core.Property} and its - * extended classes - * @param propValue - * Value of the Property specified by - * {@link org.opendaylight.controller.sal.core.Property} and its - * extended classes + * (Eg. '00:00:00:00:00:00:00:03') + * @param propertyName + * Name of the Property. Properties that can be + * configured are: description, forwarding(only for default + * container) and tier + * @param propertyValue + * Value of the Property. Description can be any string (Eg. 'Node1'), + * valid values for tier are 0, 1 and 2, and valid values for forwarding are 0 for + * reactive and 1 for proactive forwarding. * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/node/OF/00:00:00:00:00:00:00:03/property/description/Switch3
+     *
+     * 
*/ - @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propName}/{propValue}") + @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}/{propertyValue}") @PUT @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(Response.class) @StatusCodes({ - @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 201, condition = "Operation successful"), @ResponseCode(code = 400, condition = "The nodeId or configuration is invalid"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or node or configuration name is not found"), @ResponseCode(code = 406, condition = "The property cannot be configured in non-default container"), - @ResponseCode(code = 409, condition = "Unable to update configuration due to cluster conflict"), + @ResponseCode(code = 409, condition = "Unable to update configuration due to cluster conflict or conflicting description property"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) public Response addNodeProperty( @PathParam("containerName") String containerName, @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId, - @PathParam("propName") String propName, - @PathParam("propValue") String propValue) { + @PathParam("propertyName") String propertyName, + @PathParam("propertyValue") String propertyValue) { if (!isValidContainer(containerName)) { throw new ResourceNotFoundException("Container " + containerName + " does not exist."); @@ -222,9 +273,9 @@ public class SwitchNorthbound { handleNodeAvailability(containerName, nodeType, nodeId); Node node = Node.fromString(nodeType, nodeId); - Property prop = switchManager.createProperty(propName, propValue); + Property prop = switchManager.createProperty(propertyName, propertyValue); if (prop == null) { - throw new ResourceNotFoundException("Property with name " + propName + " does not exist."); + throw new ResourceNotFoundException("Property with name " + propertyName + " does not exist."); } SwitchConfig switchConfig = switchManager.getSwitchConfig(node.toString()); Map nodeProperties = (switchConfig == null) ? new HashMap() @@ -232,29 +283,36 @@ public class SwitchNorthbound { nodeProperties.put(prop.getName(), prop); SwitchConfig newSwitchConfig = new SwitchConfig(node.toString(), nodeProperties); Status status = switchManager.updateNodeConfig(newSwitchConfig); + if (status.isSuccess()) { + return Response.status(Response.Status.CREATED).build(); + } return NorthboundUtils.getResponse(status); } /** * Delete a property of a node * - *
-     * Example Request:
-     *  http://localhost:8080/controller/nb/v2/switch/default/node/OF/00:00:00:00:00:03/property/forwarding
-     * 
- * * @param containerName - * Name of the Container + * Name of the Container (Eg. 'SliceRed') * @param nodeType - * Type of the node being programmed + * Type of the node being programmed (Eg. 'OF') * @param nodeId * Node Identifier as specified by * {@link org.opendaylight.controller.sal.core.Node} + * (Eg. '00:00:00:00:00:03:01:02') * @param propertyName - * Name of the Property specified by - * {@link org.opendaylight.controller.sal.core.Property} and its - * extended classes + * Name of the Property. Properties that can be deleted are + * description, forwarding(only in default container) and tier. * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/node/OF/00:00:00:00:00:00:00:03/property/forwarding
+     *
+     * 
*/ @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}") @@ -263,6 +321,7 @@ public class SwitchNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 400, condition = "The nodeId or configuration is invalid"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"), @ResponseCode(code = 409, condition = "Unable to delete property due to cluster conflict"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) @@ -313,21 +372,56 @@ public class SwitchNorthbound { /** * - * Retrieve a list of all the node connectors and their properties in a + * Retrieve a list of all the nodeconnectors and their properties in a * given node * * @param containerName - * The container for which we want to retrieve the list + * The container for which we want to retrieve the list (Eg. + * 'default') * @param nodeType - * Type of the node being programmed + * Type of the node being programmed (Eg. 'OF') * @param nodeId * Node Identifier as specified by - * {@link org.opendaylight.controller.sal.core.Node} + * {@link org.opendaylight.controller.sal.core.Node} (Eg. + * '00:00:00:00:00:00:00:03') * @return A List of Pair each pair represents a * {@link org.opendaylight.controller.sal.core.NodeConnector} and * its corresponding * {@link org.opendaylight.controller.sal.core.Property} attached to * it. + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/node/OF/00:00:00:00:00:00:00:01
+     *
+     * Response in XML:
+     * <list>
+     *        <nodeConnectorProperties>
+     *               <nodeconnector type="OF" id="2">
+     *                      <node type="OF" id="00:00:00:00:00:00:00:01"/>
+     *               </nodeconnector>
+     *               <properties>
+     *                      <state>
+     *                             <value>1</value>
+     *                      </state>
+     *                      <config>
+     *                             <value>1</value>
+     *                      </config>
+     *                      <name>
+     *                             <value>L1_2-C2_1</value>
+     *                      </name>
+     *               </properties>
+     *        </nodeConnectorProperties>
+     * </list>
+     *
+     * Response in JSON:
+     * {"nodeConnectorProperties":[{"nodeconnector":{"@type":"OF","@id":"2","node":{"@type":"OF","@id":"00:00:00:00:00:00:00:01"}},
+     * "properties":{"state":{"value":"1"},"config":{"value":"1"},"name":{"value":"L1_2-C2_1"}}}]}
+     *
+     * 
*/ @Path("/{containerName}/node/{nodeType}/{nodeId}") @GET @@ -335,6 +429,7 @@ public class SwitchNorthbound { @TypeHint(NodeConnectors.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public NodeConnectors getNodeConnectors( @@ -382,37 +477,51 @@ public class SwitchNorthbound { } /** - * Add a Name/Bandwidth property to a node connector + * Add Bandwidth property to a node connector * * @param containerName - * Name of the Container + * Name of the Container (Eg. 'default') * @param nodeType - * Type of the node being programmed + * Type of the node being programmed (Eg. 'OF') * @param nodeId * Node Identifier as specified by * {@link org.opendaylight.controller.sal.core.Node} + * (Eg. '00:00:00:00:00:00:00:03') * @param nodeConnectorType - * Type of the node connector being programmed + * Type of the node connector being programmed (Eg. 'OF') * @param nodeConnectorId * NodeConnector Identifier as specified by * {@link org.opendaylight.controller.sal.core.NodeConnector} - * @param propName + * (Eg. '2') + * @param propertyName * Name of the Property specified by * {@link org.opendaylight.controller.sal.core.Property} and its * extended classes - * @param propValue + * Property that can be configured is bandwidth + * @param propertyValue * Value of the Property specified by * {@link org.opendaylight.controller.sal.core.Property} and its * extended classes * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/nodeconnector/OF/00:00:00:00:00:00:00:01/OF/2/property/bandwidth/1
+     *
+     * 
*/ - @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propName}/{propValue}") + @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propertyName}/{propertyValue}") @PUT @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ - @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 201, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"), + @ResponseCode(code = 409, condition = "Unable to add property due to cluster conflict"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) public Response addNodeConnectorProperty( @PathParam("containerName") String containerName, @@ -420,8 +529,8 @@ public class SwitchNorthbound { @PathParam("nodeId") String nodeId, @PathParam("nodeConnectorType") String nodeConnectorType, @PathParam("nodeConnectorId") String nodeConnectorId, - @PathParam("propName") String propName, - @PathParam("propValue") String propValue) { + @PathParam("propertyName") String propertyName, + @PathParam("propertyValue") String propertyValue) { if (!isValidContainer(containerName)) { throw new ResourceNotFoundException("Container " + containerName + " does not exist."); @@ -447,7 +556,7 @@ public class SwitchNorthbound { NodeConnector nc = NodeConnector .fromStringNoNode(nodeConnectorType, nodeConnectorId, node); - Property prop = switchManager.createProperty(propName, propValue); + Property prop = switchManager.createProperty(propertyName, propertyValue); if (prop == null) { throw new ResourceNotFoundException( RestMessages.INVALIDDATA.toString()); @@ -464,22 +573,33 @@ public class SwitchNorthbound { * Delete a property of a node connector * * @param containerName - * Name of the Container + * Name of the Container (Eg. 'default') * @param nodeType - * Type of the node being programmed + * Type of the node being programmed (Eg. 'OF') * @param nodeId * Node Identifier as specified by * {@link org.opendaylight.controller.sal.core.Node} + * (Eg. '00:00:00:00:00:00:00:01') * @param nodeConnectorType - * Type of the node connector being programmed + * Type of the node connector being programmed (Eg. 'OF') * @param nodeConnectorId * NodeConnector Identifier as specified by * {@link org.opendaylight.controller.sal.core.NodeConnector} + * (Eg. '1') * @param propertyName * Name of the Property specified by * {@link org.opendaylight.controller.sal.core.Property} and its - * extended classes + * extended classes. Property that can be deleted is bandwidth * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://.../default/nodeconnector/OF/00:00:00:00:00:00:00:01/OF/2/property/bandwidth
+     *
+     * 
*/ @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propertyName}") @@ -487,6 +607,7 @@ public class SwitchNorthbound { @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) public Response deleteNodeConnectorProperty( @@ -528,125 +649,30 @@ public class SwitchNorthbound { throw new ResourceNotFoundException(ret.getDescription()); } - /* *//** - * Retrieve a list of Span ports that were configured previously. - * - * @param containerName - * Name of the Container - * @return list of - * {@link org.opendaylight.controller.switchmanager.SpanConfig} - * resources - */ - /* - * @Path("/span-config/{containerName}") - * - * @GET - * - * @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - * - * @StatusCodes( { - * - * @ResponseCode(code = 200, condition = "Operation successful"), - * - * @ResponseCode(code = 404, condition = "The containerName is not found"), - * - * @ResponseCode(code = 503, condition = - * "One or more of Controller Services are unavailable") }) public - * List getSpanConfigList(@PathParam("containerName") String - * containerName) { ISwitchManager switchManager = (ISwitchManager) - * getIfSwitchManagerService(containerName); if (switchManager == null) { - * throw new ServiceUnavailableException("Switch Manager " + - * RestMessages.SERVICEUNAVAILABLE.toString()); } - * - * return switchManager.getSpanConfigList(); } - *//** - * Add a span configuration - * - * @param containerName - * Name of the Container - * @param config - * {@link org.opendaylight.controller.switchmanager.SpanConfig} - * in JSON or XML format - * @return Response as dictated by the HTTP Response Status code - */ - /* - * @Path("/span-config/{containerName}") - * - * @PUT - * - * @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - * - * @StatusCodes( { - * - * @ResponseCode(code = 200, condition = "Operation successful"), - * - * @ResponseCode(code = 404, condition = "The containerName is not found"), - * - * @ResponseCode(code = 503, condition = - * "One or more of Controller Services are unavailable") }) public Response - * addSpanConfig(@PathParam("containerName") String containerName, - * - * @TypeHint(SubnetConfig.class) JAXBElement config) { - * ISwitchManager switchManager = (ISwitchManager) - * getIfSwitchManagerService(containerName); if (switchManager == null) { - * throw new ServiceUnavailableException("Switch Manager " + - * RestMessages.SERVICEUNAVAILABLE.toString()); } - * - * String ret = switchManager.addSpanConfig(config.getValue()); if - * (ret.equals(ReturnString.SUCCESS.toString())) { return - * Response.status(Response.Status.CREATED).build(); } throw new - * InternalServerErrorException(ret); } - *//** - * Delete a span configuration + /** + * Save the current switch configurations * * @param containerName - * Name of the Container - * @param config - * {@link org.opendaylight.controller.switchmanager.SpanConfig} - * in JSON or XML format + * Name of the Container (Eg. 'default') * @return Response as dictated by the HTTP Response Status code - */ - /* - * @Path("/span-config/{containerName}") - * - * @DELETE - * - * @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) * - * @StatusCodes( { - * - * @ResponseCode(code = 200, condition = "Operation successful"), - * - * @ResponseCode(code = 404, condition = "The containerName is not found"), + *
      *
-     * @ResponseCode(code = 503, condition =
-     * "One or more of Controller Services are unavailable") }) public Response
-     * deleteSpanConfig(@PathParam("containerName") String containerName,
+     * Example:
      *
-     * @TypeHint(SubnetConfig.class) JAXBElement config) {
-     * ISwitchManager switchManager = (ISwitchManager)
-     * getIfSwitchManagerService(containerName); if (switchManager == null) {
-     * throw new ServiceUnavailableException("Switch Manager " +
-     * RestMessages.SERVICEUNAVAILABLE.toString()); }
+     * RequestURL:
+     * http://.../default/switch-config
      *
-     * String ret = switchManager.removeSpanConfig(config.getValue()); if
-     * (ret.equals(ReturnString.SUCCESS.toString())) { return
-     * Response.ok().build(); } throw new ResourceNotFoundException(ret); }
-     */
-
-    /**
-     * Save the current switch configurations
-     *
-     * @param containerName
-     *            Name of the Container
-     * @return Response as dictated by the HTTP Response Status code
+     * 
*/ @Path("/{containerName}/switch-config") @POST @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), + @ResponseCode(code = 500, condition = "Failed to save switch configuration. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public Response saveSwitchConfig( @PathParam("containerName") String containerName) { diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java index fb4e8229ff..3ba1eb1843 100644 --- a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java +++ b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java @@ -78,16 +78,31 @@ public class TopologyNorthboundJAXRS { * Retrieve the Topology * * @param containerName - * The container for which we want to retrieve the topology + * The container for which we want to retrieve the topology (Eg. 'default') * * @return A List of EdgeProps each EdgeProp represent an Edge of the grap * with the corresponding properties attached to it. + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/topology/default
+     *
+     * Response in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?><topology><edgeProperties><edge><tailNodeConnector id="2" type="OF"><node id="00:00:00:00:00:00:00:02" type="OF"/></tailNodeConnector><headNodeConnector id="2" type="OF"><node id="00:00:00:00:00:00:00:51" type="OF"/></headNodeConnector></edge><properties><state><value>1</value></state><config><value>1</value></config><name><value>C1_2-L2_2</value></name><timeStamp><value>1377279422032</value><name>creation</name></timeStamp></properties></edgeProperties><edgeProperties><edge><tailNodeConnector id="2" type="OF"><node id="00:00:00:00:00:00:00:51" type="OF"/></tailNodeConnector><headNodeConnector id="2" type="OF"><node id="00:00:00:00:00:00:00:02" type="OF"/></headNodeConnector></edge><properties><state><value>1</value></state><name><value>L2_2-C1_2</value></name><config><value>1</value></config><timeStamp><value>1377279423564</value><name>creation</name></timeStamp></properties></edgeProperties></topology>
+     *
+     * Response in JSON:
+     * {"edgeProperties":[{"edge":{"tailNodeConnector":{"@id":"2","@type":"OF","node":{"@id":"00:00:00:00:00:00:00:02","@type":"OF"}},"headNodeConnector":{"@id":"2","@type":"OF","node":{"@id":"00:00:00:00:00:00:00:51","@type":"OF"}}},"properties":{"timeStamp":{"value":"1377278961017","name":"creation"}}},{"edge":{"tailNodeConnector":{"@id":"2","@type":"OF","node":{"@id":"00:00:00:00:00:00:00:51","@type":"OF"}},"headNodeConnector":{"@id":"2","@type":"OF","node":{"@id":"00:00:00:00:00:00:00:02","@type":"OF"}}},"properties":{"timeStamp":{"value":"1377278961018","name":"creation"}}}]}
+     *
+     * 
*/ @Path("/{containerName}") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(Topology.class) - @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name passed was not found") }) + @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") }) public Topology getTopology(@PathParam("containerName") String containerName) { if (!NorthboundUtils.isAuthorized( @@ -121,15 +136,30 @@ public class TopologyNorthboundJAXRS { * Retrieve the user configured links * * @param containerName - * The container for which we want to retrieve the user links + * The container for which we want to retrieve the user links (Eg. 'default') * * @return A List of user configured links + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/topology/default/user-link
+     *
+     * Response in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?><topologyUserLinks><userLinks><status>Success</status><name>link1</name><srcNodeConnector>OF|2@OF|00:00:00:00:00:00:00:02</srcNodeConnector><dstNodeConnector>OF|2@OF|00:00:00:00:00:00:00:51</dstNodeConnector></userLinks></topologyUserLinks>
+     *
+     * Response in JSON:
+     * {"userLinks":{"status":"Success","name":"link1","srcNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:02","dstNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:51"}}
+     *
+     * 
*/ - @Path("/{containerName}/userLink") + @Path("/{containerName}/user-link") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(TopologyUserLinks.class) - @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name passed was not found") }) + @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") }) public TopologyUserLinks getUserLinks( @PathParam("containerName") String containerName) { @@ -161,18 +191,33 @@ public class TopologyNorthboundJAXRS { * Add an User Link * * @param containerName - * Name of the Container. The base Container is "default". + * Name of the Container (Eg. 'default') * @param TopologyUserLinkConfig * in JSON or XML format * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/topology/default/user-link/config1-content
+     *
+     * Request in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?><topologyUserLinks><status>Success</status><name>link1</name><srcNodeConnector>OF|2@OF|00:00:00:00:00:00:00:02</srcNodeConnector><dstNodeConnector>OF|2@OF|00:00:00:00:00:00:00:51</dstNodeConnector></topologyUserLinks>
+     *
+     * Request in JSON:
+     * {"status":"Success","name":"link1","srcNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:02","dstNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:51"}
+     *
+     * 
*/ - - @Path("/{containerName}/userLink") + @Path("/{containerName}/user-link") @POST @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 201, condition = "User Link added successfully"), - @ResponseCode(code = 404, condition = "The Container Name passed was not found"), + @ResponseCode(code = 404, condition = "The Container Name was not found"), @ResponseCode(code = 409, condition = "Failed to add User Link due to Conflicting Name"), @ResponseCode(code = 500, condition = "Failed to add User Link. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) @@ -205,15 +250,24 @@ public class TopologyNorthboundJAXRS { * Delete an User Link * * @param containerName - * Name of the Container. The base Container is "default". + * Name of the Container (Eg. 'default') * @param name - * Name of the Link Configuration + * Name of the Link Configuration (Eg. 'config1') * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/topology/default/user-link/config1
+     *
+     * 
*/ - - @Path("/{containerName}/userLink/{name}") + @Path("/{containerName}/user-link/{name}") @DELETE @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 404, condition = "The Container Name or Link Configuration Name was not found"), @@ -242,5 +296,4 @@ public class TopologyNorthboundJAXRS { } throw new ResourceNotFoundException(ret.getDescription()); } - } diff --git a/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/Activator.java b/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/Activator.java index 76c38664d4..79edd93c67 100644 --- a/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/Activator.java +++ b/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/Activator.java @@ -40,6 +40,7 @@ public class Activator extends ComponentActivatorAbstractBase { * are done by the ComponentActivatorAbstractBase. * */ + @Override public void init() { Node.NodeIDType.registerIDType("STUB", Integer.class); NodeConnector.NodeConnectorIDType.registerIDType("STUB", Integer.class, "STUB"); @@ -50,6 +51,7 @@ public class Activator extends ComponentActivatorAbstractBase { * ComponentActivatorAbstractBase * */ + @Override public void destroy() { Node.NodeIDType.unRegisterIDType("STUB"); NodeConnector.NodeConnectorIDType.unRegisterIDType("STUB"); @@ -64,6 +66,7 @@ public class Activator extends ComponentActivatorAbstractBase { * instantiated in order to get an fully working implementation * Object */ + @Override public Object[] getImplementations() { Object[] res = { ReadService.class, InventoryService.class }; return res; @@ -84,6 +87,7 @@ public class Activator extends ComponentActivatorAbstractBase { * per-container different behavior if needed, usually should not * be the case though. */ + @Override public void configureInstance(Component c, Object imp, String containerName) { if (imp.equals(ReadService.class)) { // export the service to be used by SAL @@ -104,11 +108,18 @@ public class Activator extends ComponentActivatorAbstractBase { } } + @Override public Object[] getGlobalImplementations() { - Object[] res = { FlowProgrammerService.class, StubNodeFactory.class, StubNodeConnectorFactory.class }; + Object[] res = + { + FlowProgrammerService.class, + StubNodeFactory.class, + StubNodeConnectorFactory.class, + InventoryService.class }; return res; } + @Override public void configureGlobalInstance(Component c, Object imp){ if (imp.equals(FlowProgrammerService.class)) { // export the service to be used by SAL @@ -136,6 +147,17 @@ public class Activator extends ComponentActivatorAbstractBase { props.put("protocolName", "STUB"); c.setInterface(INodeConnectorFactory.class.getName(), props); } - + if (imp.equals(InventoryService.class)) { + // export the service to be used by SAL + Dictionary props = new Hashtable(); + // Set the protocolPluginType property which will be used + // by SAL + props.put(GlobalConstants.PROTOCOLPLUGINTYPE.toString(), "STUB"); + props.put("scope", "Global"); + c.setInterface(IPluginInInventoryService.class.getName(), props); + c.add(createServiceDependency().setService(IPluginOutInventoryService.class, "(scope=Global)") + .setCallbacks("setPluginOutInventoryServices", "unsetPluginOutInventoryServices") + .setRequired(true)); + } } } diff --git a/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/InventoryService.java b/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/InventoryService.java index 22a4343f33..b94ffec1dd 100644 --- a/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/InventoryService.java +++ b/opendaylight/protocol_plugins/stub/src/main/java/org/opendaylight/controller/protocol_plugins/stub/internal/InventoryService.java @@ -12,11 +12,11 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; import org.apache.felix.dm.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.opendaylight.controller.sal.core.Actions; import org.opendaylight.controller.sal.core.Bandwidth; import org.opendaylight.controller.sal.core.Buffers; @@ -29,7 +29,9 @@ import org.opendaylight.controller.sal.core.Property; import org.opendaylight.controller.sal.core.State; import org.opendaylight.controller.sal.core.Tables; import org.opendaylight.controller.sal.core.TimeStamp; +import org.opendaylight.controller.sal.core.UpdateType; import org.opendaylight.controller.sal.inventory.IPluginInInventoryService; +import org.opendaylight.controller.sal.inventory.IPluginOutInventoryService; import org.opendaylight.controller.sal.utils.NodeCreator; import org.opendaylight.controller.sal.utils.NodeConnectorCreator; @@ -55,6 +57,22 @@ public class InventoryService implements IPluginInInventoryService { // global // container // only + private final Set pluginOutInventoryServices = + new CopyOnWriteArraySet(); + + public void setPluginOutInventoryServices(IPluginOutInventoryService service) { + logger.trace("Got a service set request {}", service); + if (this.pluginOutInventoryServices != null) { + this.pluginOutInventoryServices.add(service); + } + } + + public void unsetPluginOutInventoryServices(IPluginOutInventoryService service) { + logger.trace("Got a service UNset request"); + if (this.pluginOutInventoryServices != null) { + this.pluginOutInventoryServices.remove(service); + } + } /** * Function called by the dependency manager when all the required @@ -173,6 +191,29 @@ public class InventoryService implements IPluginInInventoryService { void start() { } + /** + * Method called when the plugin has exposed it's services, this will be + * used to publish the updates so connection manager can think the + * connection is local + */ + void started() { + // update sal and discovery + for (IPluginOutInventoryService service : pluginOutInventoryServices) { + for (Node node : nodeProps.keySet()) { + Set props = new HashSet(nodeProps.get(node) + .values()); + service.updateNode(node, UpdateType.ADDED, props); + logger.trace("Adding Node {} with props {}", node, props); + } + for (NodeConnector nc : nodeConnectorProps.keySet()) { + Set props = new HashSet(nodeConnectorProps.get(nc) + .values()); + service.updateNodeConnector(nc, UpdateType.ADDED, props); + logger.trace("Adding NodeConnectors {} with props {}", nc, props); + } + } + } + /** * Function called by the dependency manager before the services exported by * the component are unregistered, this will be followed by a "destroy ()" @@ -180,6 +221,7 @@ public class InventoryService implements IPluginInInventoryService { * */ void stop() { + pluginOutInventoryServices.clear(); } /** diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/Match.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/Match.java index 9bab839198..30c25af57f 100644 --- a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/Match.java +++ b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/Match.java @@ -246,93 +246,195 @@ public class Match implements Cloneable, Serializable { /** * Check whether the current match conflicts with the passed filter match * This match conflicts with the filter if for at least a MatchType defined - * in the filter match, the respective MatchFields differ or are not compatible + * in the filter match, the respective MatchFields differ or are not + * compatible + * + * In other words the function returns true if the set of packets described + * by one match and the set of packets described by the other match are + * disjoint. Equivalently, if the intersection of the two sets of packets + * described by the two matches is an empty. * * For example, Let's suppose the filter has the following MatchFields: * DL_TYPE = 0x800 - * NW_DST = 172.20.30.110/24 + * NW_DST = 172.20.30.110/24 * * while this match has the following MatchFields: + * DL_TYPE = 0x800 * NW_DST = 172.20.30.45/24 * TP_DST = 80 * - * Then the function would return false as the two Match are not conflicting + * Then the function would return false as the two Match are not + * conflicting. * - * Note: the mask value is taken into account only for MatchType.NW_SRC and MatchType.NW_DST + * Note: the mask value is taken into account only for MatchType.NW_SRC and + * MatchType.NW_DST * - * @param match the MAtch describing the filter - * @return true if the match is conflicting with the filter, false otherwise + * @param match + * the Match describing the filter + * @return true if the set of packets described by one match and the set of + * packets described by the other match are disjoint, false + * otherwise */ public boolean conflictWithFilter(Match filter) { - // Iterate through the MatchType defined in the filter - for (MatchType type : filter.getMatchesList()) { + return !this.intersetcs(filter); + } + + /** + * Merge the current Match fields with the fields of the filter Match. A + * check is first run to see if this Match is compatible with the filter + * Match. If it is not, the merge is not attempted. + * + * The result is the match object representing the intersection of the set + * of packets described by this match with the set of packets described by + * the filter match. If the intersection of the two sets is empty, the + * return match will be null. + * + * @param filter + * the match with which attempting the merge + * @return a new Match object describing the set of packets represented by + * the intersection of this and the filter matches. null if the + * intersection is empty. + */ + public Match mergeWithFilter(Match filter) { + return this.getIntersection(filter); + } + + /** + * Return the match representing the intersection of the set of packets + * described by this match with the set of packets described by the other + * match. Such as m.getIntersection(m) == m, m.getIntersection(u) == m and + * m.getIntersection(o) == o where u is an empty match (universal set, all + * packets) and o is the null match (empty set). + * + * @param other + * the match with which computing the intersection + * @return a new Match object representing the intersection of the set of + * packets described by this match with the set of packets described + * by the other match. null when the intersection is the empty set. + */ + public Match getIntersection(Match other) { + // If no intersection, return the empty set + if (!this.intersetcs(other)) { + return null; + } + // Check if any of the two is the universal match + if (this.getMatches() == 0) { + return other.clone(); + } + if (other.getMatches() == 0) { + return this.clone(); + } + // Derive the intersection + Match intersection = new Match(); + for (MatchType type : MatchType.values()) { + if (this.isAny(type) && other.isAny(type)) { + continue; + } if (this.isAny(type)) { + intersection.setField(other.getField(type).clone()); + continue; + } else if (other.isAny(type)) { + intersection.setField(this.getField(type).clone()); + continue; + } + // Either they are equal or it is about IP address + switch (type) { + // When it is about IP address, take the wider prefix address + // between the twos + case NW_SRC: + case NW_DST: + MatchField thisField = this.getField(type); + MatchField otherField = other.getField(type); + InetAddress thisAddress = (InetAddress) thisField.getValue(); + InetAddress otherAddress = (InetAddress) otherField.getValue(); + InetAddress thisMask = (InetAddress) thisField.getMask(); + InetAddress otherMask = (InetAddress) otherField.getMask(); + + int thisMaskLen = (thisMask == null) ? ((thisAddress instanceof Inet4Address) ? 32 : 128) : NetUtils + .getSubnetMaskLength(thisMask); + int otherMaskLen = (otherMask == null) ? ((otherAddress instanceof Inet4Address) ? 32 : 128) : NetUtils + .getSubnetMaskLength(otherMask); + if (otherMaskLen < thisMaskLen) { + intersection.setField(new MatchField(type, NetUtils.getSubnetPrefix(otherAddress, otherMaskLen), + otherMask)); + } else { + intersection.setField(new MatchField(type, NetUtils.getSubnetPrefix(thisAddress, thisMaskLen), + thisMask)); + } + break; + default: + // this and other match field are equal for this type, pick this + // match field + intersection.setField(this.getField(type).clone()); + } + } + return intersection; + } + + /** + * Checks whether the intersection of the set of packets described by this + * match with the set of packets described by the other match is non empty + * + * For example, if this match is: DL_SRC = 00:cc:bb:aa:11:22 + * + * and the other match is: DL_TYPE = 0x800 NW_SRC = 1.2.3.4 + * + * then their respective matching packets set intersection is non empty: + * DL_SRC = 00:cc:bb:aa:11:22 DL_TYPE = 0x800 NW_SRC = 1.2.3.4 + * + * @param other + * the other match with which testing the intersection + * @return true if the intersection of the respective matching packets sets + * is non empty + */ + public boolean intersetcs(Match other) { + // No intersection with the empty set + if (other == null) { + return false; + } + // Always intersection with the universal set + if (this.getMatches() == 0 || other.getMatches() == 0) { + return true; + } + // Iterate through the MatchType defined in the filter + for (MatchType type : MatchType.values()) { + if (this.isAny(type) || other.isAny(type)) { continue; } MatchField thisField = this.getField(type); - MatchField filterField = filter.getField(type); + MatchField otherField = other.getField(type); switch (type) { case DL_SRC: case DL_DST: - if (Arrays.equals((byte[]) thisField.getValue(), - (byte[]) filterField.getValue())) { + if (!Arrays.equals((byte[]) thisField.getValue(), (byte[]) otherField.getValue())) { return false; } break; case NW_SRC: case NW_DST: InetAddress thisAddress = (InetAddress) thisField.getValue(); - InetAddress filterAddress = (InetAddress) filterField - .getValue(); + InetAddress otherAddress = (InetAddress) otherField.getValue(); // Validity check - if (thisAddress instanceof Inet4Address - && filterAddress instanceof Inet6Address - || thisAddress instanceof Inet6Address - && filterAddress instanceof Inet4Address) { - return true; + if (thisAddress instanceof Inet4Address && otherAddress instanceof Inet6Address + || thisAddress instanceof Inet6Address && otherAddress instanceof Inet4Address) { + return false; } InetAddress thisMask = (InetAddress) thisField.getMask(); - InetAddress filterMask = (InetAddress) filterField.getMask(); - // thisAddress has to be in same subnet of filterAddress - if (NetUtils.inetAddressConflict(thisAddress, filterAddress, - thisMask, filterMask)) { - return true; + InetAddress otherMask = (InetAddress) otherField.getMask(); + if (NetUtils.inetAddressConflict(thisAddress, otherAddress, thisMask, otherMask) + && NetUtils.inetAddressConflict(otherAddress, thisAddress, otherMask, thisMask)) { + return false; } break; default: - if (!thisField.getValue().equals(filterField.getValue())) { - return true; - } - } - //TODO: check v4 v6 incompatibility - } - return false; - } - - /** - * Merge the current Match fields with the fields of the filter Match - * A check is first run to see if this Match is compatible with the - * filter Match. If it is not, the merge is not attempted. - * - * - * @param filter - * @return - */ - public Match mergeWithFilter(Match filter) { - if (!this.conflictWithFilter(filter)) { - /* - * No conflict with the filter - * We can copy over the fields which this match does not have - */ - for (MatchType type : filter.getMatchesList()) { - if (this.isAny(type)) { - this.setField(filter.getField(type).clone()); + if (!thisField.getValue().equals(otherField.getValue())) { + return false; } } } - return this; + return true; } @Override diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchField.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchField.java index 3b8a295216..54be4c6718 100644 --- a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchField.java +++ b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchField.java @@ -184,7 +184,8 @@ public class MatchField implements Cloneable, Serializable { @Override public String toString() { - return type + "(" + getValueString() + "," + getMaskString() + ")"; + return (mask == null) ? String.format("%s(%s)", getTypeString(), getValueString()) : + String.format("%s(%s,%s)", getTypeString(), getValueString(), getMaskString()); } @Override diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/NetUtils.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/NetUtils.java index 0fce4f4b0a..e94021119d 100644 --- a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/NetUtils.java +++ b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/NetUtils.java @@ -247,11 +247,18 @@ public abstract class NetUtils { * Checks if the test address and mask conflicts with the filter address and * mask * - * For example: testAddress: 172.28.2.23 testMask: 255.255.255.0 - * filtAddress: 172.28.1.10 testMask: 255.255.255.0 conflict + * For example: + * testAddress: 172.28.2.23 + * testMask: 255.255.255.0 + * filterAddress: 172.28.1.10 + * testMask: 255.255.255.0 + * do conflict * - * testAddress: 172.28.2.23 testMask: 255.255.255.0 filtAddress: 172.28.1.10 - * testMask: 255.255.0.0 do not conflict + * testAddress: 172.28.2.23 + * testMask: 255.255.255.0 + * filterAddress: 172.28.1.10 + * testMask: 255.255.0.0 + * do not conflict * * Null parameters are permitted * @@ -275,7 +282,8 @@ public abstract class NetUtils { int testMaskLen = (testMask == null) ? ((testAddress instanceof Inet4Address) ? 32 : 128) : NetUtils .getSubnetMaskLength(testMask); - int filterMaskLen = NetUtils.getSubnetMaskLength(filterMask); + int filterMaskLen = (filterMask == null) ? ((testAddress instanceof Inet4Address) ? 32 : 128) : NetUtils + .getSubnetMaskLength(filterMask); // Mask length check. Test mask has to be more specific than filter one if (testMaskLen < filterMaskLen) { @@ -464,7 +472,7 @@ public abstract class NetUtils { /* * Following utilities are useful when you need to compare or bit shift java - * primitive type variable which are inerently signed + * primitive type variable which are inherently signed */ /** * Returns the unsigned value of the passed byte variable diff --git a/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/match/MatchTest.java b/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/match/MatchTest.java index ae78e1acc8..f3c7a95b0e 100644 --- a/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/match/MatchTest.java +++ b/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/match/MatchTest.java @@ -523,4 +523,98 @@ public class MatchTest { Assert.assertTrue(field.getValue().equals(new Short(vlan))); Assert.assertTrue(field.isValid()); } + + @Test + public void testIntersection() throws UnknownHostException { + Short ethType = Short.valueOf((short)0x800); + InetAddress ip1 = InetAddress.getByName("1.1.1.1"); + InetAddress ip2 = InetAddress.getByName("1.1.1.0"); + InetAddress ipm2 = InetAddress.getByName("255.255.255.0"); + InetAddress ip3 = InetAddress.getByName("1.3.0.0"); + InetAddress ipm3 = InetAddress.getByName("255.255.0.0"); + + Match m1 = new Match(); + m1.setField(MatchType.DL_TYPE, ethType); + m1.setField(MatchType.NW_SRC, ip1); + + Match m2 = new Match(); + m2.setField(MatchType.DL_TYPE, ethType); + m2.setField(MatchType.NW_SRC, ip2, ipm2); + + Match m3 = new Match(); + m3.setField(MatchType.DL_TYPE, ethType); + m3.setField(MatchType.NW_SRC, ip3, ipm3); + m3.setField(MatchType.NW_PROTO, IPProtocols.TCP.byteValue()); + + Match m3r = m3.reverse(); + Assert.assertTrue(m3.intersetcs(m3r)); + + Assert.assertTrue(m1.intersetcs(m2)); + Assert.assertTrue(m2.intersetcs(m1)); + Assert.assertFalse(m1.intersetcs(m3)); + Assert.assertTrue(m1.intersetcs(m3r)); + Assert.assertFalse(m3.intersetcs(m1)); + Assert.assertTrue(m3.intersetcs(m1.reverse())); + Assert.assertFalse(m2.intersetcs(m3)); + Assert.assertFalse(m3.intersetcs(m2)); + Assert.assertTrue(m2.intersetcs(m3r)); + + + Match i = m1.getIntersection(m2); + Assert.assertTrue(((Short)i.getField(MatchType.DL_TYPE).getValue()).equals(ethType)); + Assert.assertTrue(((InetAddress)i.getField(MatchType.NW_SRC).getValue()).equals(ip2)); + Assert.assertTrue(((InetAddress)i.getField(MatchType.NW_SRC).getMask()).equals(ipm2)); + + // Empty set + i = m2.getIntersection(m3); + Assert.assertNull(i); + + Match m4 = new Match(); + m4.setField(MatchType.DL_TYPE, ethType); + m4.setField(MatchType.NW_PROTO, IPProtocols.TCP.byteValue()); + Assert.assertTrue(m4.intersetcs(m3)); + + Match m5 = new Match(); + m5.setField(MatchType.DL_TYPE, ethType); + m3.setField(MatchType.NW_SRC, ip3, ipm3); + m5.setField(MatchType.NW_PROTO, IPProtocols.UDP.byteValue()); + Assert.assertFalse(m5.intersetcs(m3)); + Assert.assertFalse(m5.intersetcs(m4)); + Assert.assertTrue(m5.intersetcs(m5)); + Assert.assertFalse(m3.intersetcs(m5)); + Assert.assertFalse(m4.intersetcs(m5)); + + + Match i2 = m4.getIntersection(m3); + Assert.assertFalse(i2.getMatches() == 0); + Assert.assertFalse(i2.getMatchesList().isEmpty()); + Assert.assertTrue(((InetAddress)i2.getField(MatchType.NW_SRC).getValue()).equals(ip3)); + Assert.assertTrue(((InetAddress)i2.getField(MatchType.NW_SRC).getMask()).equals(ipm3)); + Assert.assertTrue(((Byte)i2.getField(MatchType.NW_PROTO).getValue()).equals(IPProtocols.TCP.byteValue())); + + byte src[] = {(byte)0, (byte)0xab,(byte)0xbc,(byte)0xcd,(byte)0xde,(byte)0xef}; + byte dst[] = {(byte)0x10, (byte)0x11,(byte)0x12,(byte)0x13,(byte)0x14,(byte)0x15}; + Short srcPort = (short)1024; + Short dstPort = (short)80; + + // Check identity + Match m6 = new Match(); + m6.setField(MatchType.DL_SRC, src); + m6.setField(MatchType.DL_DST, dst); + m6.setField(MatchType.NW_SRC, ip2, ipm2); + m6.setField(MatchType.NW_DST, ip3, ipm3); + m6.setField(MatchType.NW_PROTO, IPProtocols.UDP.byteValue()); + m6.setField(MatchType.TP_SRC, srcPort); + m6.setField(MatchType.TP_DST, dstPort); + Assert.assertTrue(m6.intersetcs(m6)); + Assert.assertTrue(m6.getIntersection(m6).equals(m6)); + + // Empty match, represents the universal set (all packets) + Match u = new Match(); + Assert.assertTrue(m6.getIntersection(u).equals(m6)); + Assert.assertTrue(u.getIntersection(m6).equals(m6)); + + // No intersection with null match, empty set + Assert.assertNull(m6.getIntersection(null)); + } } diff --git a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java index 15ad9e9887..781242a969 100644 --- a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java +++ b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java @@ -636,7 +636,7 @@ public class Devices implements IDaylightWeb { @RequestMapping(value = "/nodeports") @ResponseBody - public Map getNodePorts(HttpServletRequest request, + public List getNodePorts(HttpServletRequest request, @RequestParam(required = false) String container) { String containerName = (container == null) ? GlobalConstants.DEFAULT .toString() : container; @@ -647,18 +647,16 @@ public class Devices implements IDaylightWeb { return null; } - ISwitchManager switchManager = (ISwitchManager) ServiceHelper .getInstance(ISwitchManager.class, containerName, this); if (switchManager == null) { return null; } - - Map nodes = new HashMap(); - Map port; + List nodeJsonBeans = new ArrayList(); for (Switch node : switchManager.getNetworkDevices()) { - port = new HashMap(); // new port + NodeJsonBean nodeJsonBean = new NodeJsonBean(); + List port = new ArrayList(); Set nodeConnectorSet = node.getNodeConnectors(); if (nodeConnectorSet != null) { @@ -666,15 +664,17 @@ public class Devices implements IDaylightWeb { String nodeConnectorName = ((Name) switchManager .getNodeConnectorProp(nodeConnector, Name.NamePropName)).getValue(); - port.put((Short) nodeConnector.getID(), nodeConnectorName + port.add(nodeConnectorName + "(" + nodeConnector.getID() + ")"); } } - - nodes.put(node.getNode().toString(), port); + nodeJsonBean.setNodeId(node.getNode().toString()); + nodeJsonBean.setNodeName(getNodeDesc(node.getNode().toString(), containerName)); + nodeJsonBean.setNodePorts(port); + nodeJsonBeans.add(nodeJsonBean); } - return nodes; + return nodeJsonBeans; } @RequestMapping(value = "/spanPorts/add", method = RequestMethod.GET) diff --git a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/NodeJsonBean.java b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/NodeJsonBean.java new file mode 100644 index 0000000000..0fd933a5ec --- /dev/null +++ b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/NodeJsonBean.java @@ -0,0 +1,42 @@ +/* + * 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.devices.web; + +import java.util.List; + +public class NodeJsonBean { + String nodeId; + String nodeName; + List nodePorts; + + public String getNodeId() { + return nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public List getNodePorts() { + return nodePorts; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public void setNodePorts(List port) { + this.nodePorts = port; + } + +} diff --git a/opendaylight/web/devices/src/main/resources/js/page.js b/opendaylight/web/devices/src/main/resources/js/page.js index 6916be6798..048c9e993e 100644 --- a/opendaylight/web/devices/src/main/resources/js/page.js +++ b/opendaylight/web/devices/src/main/resources/js/page.js @@ -1161,11 +1161,11 @@ one.f.switchmanager.spanPortConfig = { // bind onchange $select.change(function() { // retrieve port value - var node = $(this).find('option:selected').attr('value'); - one.f.switchmanager.spanPortConfig.registry['currentNode'] = node; + var nodeId = $(this).find('option:selected').attr('value'); + one.f.switchmanager.spanPortConfig.registry['currentNode'] = nodeId; var $ports = $('#' + one.f.switchmanager.spanPortConfig.id.modal.form.port); - var ports = nodeports[node]; - one.lib.form.select.inject($ports, ports); + var ports = one.f.switchmanager.spanPortConfig.registry['nodePorts'][nodeId] + one.lib.form.select.inject($ports, ports); }); $fieldset.append($label).append($select); @@ -1182,10 +1182,14 @@ one.f.switchmanager.spanPortConfig = { ajax: { nodes: function(callback) { $.getJSON(one.f.switchmanager.rootUrl + "/nodeports", function(data) { - var nodes = one.f.switchmanager.spanPortConfig.modal.data.nodes(data); - var nodeports = data; - one.f.switchmanager.spanPortConfig.registry['nodeports'] = nodeports; - callback(nodes, nodeports); + var nodes = {}; + var nodePorts = {}; + $(data).each(function(index, node) { + nodes[node.nodeId] = node.nodeName; + nodePorts[node.nodeId] = node.nodePorts; + }); + one.f.switchmanager.spanPortConfig.registry['nodePorts'] = nodePorts; + callback(nodes, nodePorts); }); }, saveSpanPortConfig: function(requestData, callback) { @@ -1196,15 +1200,6 @@ one.f.switchmanager.spanPortConfig = { }); } }, - data : { - nodes : function(data) { - result = {}; - $.each(data, function(key, value) { - result[key] = key; - }); - return result; - } - }, footer : function() { var footer = []; var saveButton = one.lib.dashlet.button.single("Save", one.f.switchmanager.spanPortConfig.id.modal.save, "btn-success", ""); diff --git a/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java b/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java index 9444360eec..a96d3efcec 100644 --- a/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java +++ b/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.flows.web; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -77,13 +78,16 @@ public class Flows implements IDaylightWeb { @RequestMapping(value = "/main") @ResponseBody - public Map getFlows(HttpServletRequest request, @RequestParam(required = false) String container) { - String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; + public Map getFlows(HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; // Derive the privilege this user has on the current container String userName = request.getUserPrincipal().getName(); - Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this); - if (privilege == Privilege.NONE) { + Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, + containerName, this); + if (privilege == Privilege.NONE) { return null; } @@ -114,7 +118,7 @@ public class Flows implements IDaylightWeb { flowSet.add(entry); } - Map output = new HashMap(2); + Map output = new HashMap(2); output.put("flows", flowSet); output.put("privilege", privilege); return output; @@ -122,12 +126,15 @@ public class Flows implements IDaylightWeb { @RequestMapping(value = "/node-ports") @ResponseBody - public Map getNodePorts(HttpServletRequest request, @RequestParam(required = false) String container) { - String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; + public Map getNodePorts(HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; // Derive the privilege this user has on the current container String userName = request.getUserPrincipal().getName(); - if (DaylightWebUtil.getContainerPrivilege(userName, containerName, this) == Privilege.NONE) { + if (DaylightWebUtil + .getContainerPrivilege(userName, containerName, this) == Privilege.NONE) { return null; } @@ -171,12 +178,15 @@ public class Flows implements IDaylightWeb { @RequestMapping(value = "/node-flows") @ResponseBody - public Map getNodeFlows(HttpServletRequest request, @RequestParam(required = false) String container) { - String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; + public Map getNodeFlows(HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; // Derive the privilege this user has on the current container String userName = request.getUserPrincipal().getName(); - if (DaylightWebUtil.getContainerPrivilege(userName, containerName, this) == Privilege.NONE) { + if (DaylightWebUtil + .getContainerPrivilege(userName, containerName, this) == Privilege.NONE) { return null; } @@ -199,10 +209,12 @@ public class Flows implements IDaylightWeb { List flows = frm.getStaticFlows(node); String nodeDesc = node.toString(); - SwitchConfig config = switchManager.getSwitchConfig(node - .toString()); - if ((config != null) && (config.getProperty(Description.propertyName) != null)) { - nodeDesc = ((Description) config.getProperty(Description.propertyName)).getValue(); + SwitchConfig config = switchManager + .getSwitchConfig(node.toString()); + if ((config != null) + && (config.getProperty(Description.propertyName) != null)) { + nodeDesc = ((Description) config + .getProperty(Description.propertyName)).getValue(); } nodes.put(nodeDesc, flows.size()); @@ -216,12 +228,15 @@ public class Flows implements IDaylightWeb { public String actionFlow(@RequestParam(required = true) String action, @RequestParam(required = false) String body, @RequestParam(required = true) String nodeId, - HttpServletRequest request, @RequestParam(required = false) String container) { - String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; + HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; // Authorization check String userName = request.getUserPrincipal().getName(); - if (DaylightWebUtil.getContainerPrivilege(userName, containerName, this) != Privilege.WRITE) { + if (DaylightWebUtil + .getContainerPrivilege(userName, containerName, this) != Privilege.WRITE) { return "Operation not authorized"; } @@ -238,7 +253,8 @@ public class Flows implements IDaylightWeb { Status result = new Status(StatusCode.BADREQUEST, "Invalid request"); if (action.equals("add")) { result = frm.addStaticFlow(flow); - DaylightWebUtil.auditlog("Flow", userName, "added", flow.getName(), containerName); + DaylightWebUtil.auditlog("Flow", userName, "added", flow.getName(), + containerName); } return (result.isSuccess()) ? StatusCode.SUCCESS.toString() : result @@ -250,12 +266,15 @@ public class Flows implements IDaylightWeb { public String removeFlow(@PathVariable("nodeId") String nodeId, @PathVariable("name") String name, @RequestParam(required = true) String action, - HttpServletRequest request, @RequestParam(required = false) String container) { - String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; + HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; // Authorization check String userName = request.getUserPrincipal().getName(); - if (DaylightWebUtil.getContainerPrivilege(userName, containerName, this) != Privilege.WRITE) { + if (DaylightWebUtil + .getContainerPrivilege(userName, containerName, this) != Privilege.WRITE) { return "Operation not authorized"; } @@ -272,13 +291,15 @@ public class Flows implements IDaylightWeb { } if (action.equals("remove")) { result = frm.removeStaticFlow(name, node); - if(result.isSuccess()) { - DaylightWebUtil.auditlog("Flow", userName, "removed", name, containerName); + if (result.isSuccess()) { + DaylightWebUtil.auditlog("Flow", userName, "removed", name, + containerName); } } else if (action.equals("toggle")) { result = frm.toggleStaticFlowStatus(name, node); - if(result.isSuccess()) { - DaylightWebUtil.auditlog("Flow", userName, "toggled", name, containerName); + if (result.isSuccess()) { + DaylightWebUtil.auditlog("Flow", userName, "toggled", name, + containerName); } } else { result = new Status(StatusCode.BADREQUEST, "Unknown action"); @@ -288,10 +309,58 @@ public class Flows implements IDaylightWeb { .getDescription(); } + @SuppressWarnings("unchecked") + @RequestMapping(value = "/flow/deleteFlows", method = RequestMethod.POST) + @ResponseBody + public String removeSelectedFlows( + @RequestParam(required = false) String body, + HttpServletRequest request, + @RequestParam(required = false) String container) { + String containerName = (container == null) ? GlobalConstants.DEFAULT + .toString() : container; + + // Authorization check + String userName = request.getUserPrincipal().getName(); + if (DaylightWebUtil + .getContainerPrivilege(userName, containerName, this) != Privilege.WRITE) { + return "Operation not authorized"; + } + + IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper + .getInstance(IForwardingRulesManager.class, containerName, this); + if (frm == null) { + return "Forwarding Rules Manager is not available"; + } + + Gson gson = new Gson(); + List> flowList = new ArrayList>(); + flowList = gson.fromJson(body, flowList.getClass()); + Status result = new Status(StatusCode.BADREQUEST, "Invalid request"); + String status = ""; + for (Map flowEntry : flowList) { + Node node = Node.fromString(flowEntry.get("node")); + result = frm.removeStaticFlow(flowEntry.get("name"), node); + if (result.isSuccess()) { + DaylightWebUtil.auditlog("Flow", userName, "removed", + flowEntry.get("name"), containerName); + } else { + status = flowEntry.get("name") + ", " + status; + } + } + if (!status.equals("")) { + return "Could not remove " + + status.substring(0, status.length() - 2) + " Flow(s)"; + } else { + return "Success"; + } + } + private String getNodeDesc(Node node, ISwitchManager switchManager) { - Description desc = (Description) switchManager.getNodeProp(node, Description.propertyName); + Description desc = (Description) switchManager.getNodeProp(node, + Description.propertyName); String description = (desc == null) ? "" : desc.getValue(); - return (description.isEmpty() || description.equalsIgnoreCase("none")) ? node.toString() : description; + return (description.isEmpty() || description.equalsIgnoreCase("none")) ? node + .toString() : description; } } diff --git a/opendaylight/web/flows/src/main/resources/js/page.js b/opendaylight/web/flows/src/main/resources/js/page.js index 0f1eabb859..3dcf9e3006 100644 --- a/opendaylight/web/flows/src/main/resources/js/page.js +++ b/opendaylight/web/flows/src/main/resources/js/page.js @@ -48,7 +48,8 @@ one.f.address = { main : "/main", flows : "/node-flows", nodes : "/node-ports", - flow : "/flow" + flow : "/flow", + deleteFlows:"/flow/deleteFlows" } } @@ -251,9 +252,11 @@ one.f.flows = { id : { dashlet : { add : "one_f_flows_id_dashlet_add", + removeMultiple : "one_f_flows_id_dashlet_removeMultiple", remove : "one_f_flows_id_dashlet_remove", toggle : "one_f_flows_id_dashlet_toggle", - datagrid: "one_f_flows_id_dashlet_datagrid" + datagrid : "one_f_flows_id_dashlet_datagrid", + selectAllFlows : "one_f_flows_id_dashlet_selectAllFlows" }, modal : { install : "one_f_flows_id_modal_install", @@ -327,6 +330,36 @@ one.f.flows = { $modal.modal(); }); $dashlet.append($button); + var button = one.lib.dashlet.button.single("Remove Flow Entry", one.f.flows.id.dashlet.removeMultiple, "btn-primary", "btn-mini"); + var $button = one.lib.dashlet.button.button(button); + + $button.click(function() { + var checkedCheckBoxes = $('.flowEntry[type=checkbox]:checked'); + + var requestData = []; + + var resource = {}; + checkedCheckBoxes.each(function(index, value) { + var flowEntry = {}; + flowEntry['name'] = checkedCheckBoxes[index].name; + flowEntry['node'] = checkedCheckBoxes[index].getAttribute("node"); + requestData.push(flowEntry); + }); + resource['body'] = JSON.stringify(requestData); + + $.post(one.f.address.root+one.f.address.flows.deleteFlows, resource, function(response) { + if(response == "Success") { + one.lib.alert("Flow(s) successfully removed"); + } else { + one.lib.alert(response); + } + one.main.dashlet.right.bottom.empty(); + one.f.detail.dashlet(one.main.dashlet.right.bottom); + one.main.dashlet.left.top.empty(); + one.f.flows.dashlet(one.main.dashlet.left.top); + }); + }); + $dashlet.append($button); } @@ -339,7 +372,12 @@ one.f.flows = { $dashlet.append($gridHTML); var dataSource = one.f.flows.data.flowsDataGrid(data); $("#" + one.f.flows.id.dashlet.datagrid).datagrid({dataSource: dataSource}).on("loaded", function() { - $("#" + one.f.flows.id.dashlet.datagrid).find("tbody tr").each(function(index, tr) { + $("#"+one.f.flows.id.dashlet.datagrid.selectAllFlows).click(function() { + $("#" + one.f.flows.id.dashlet.datagrid).find(':checkbox').prop('checked', + $("#"+one.f.flows.id.dashlet.datagrid.selectAllFlows).is(':checked')); + }); + + $("#" + one.f.flows.id.dashlet.datagrid).find("tbody tr").each(function(index, tr) { $tr = $(tr); $span = $("td span", $tr); var flowstatus = $span.data("flowstatus"); @@ -357,10 +395,22 @@ one.f.flows = { }); // attach click event $tr.click(function() { - var $td = $($(this).find("td")[0]); + var $td = $($(this).find("td")[1]); var id = $td.text(); var node = $td.find("span").data("nodeid"); - one.f.flows.detail(id, node); + one.f.flows.detail(id, node); + }); + $(".flowEntry").click(function(){ + if (!$('.flowEntry[type=checkbox]:not(:checked)').length) { + $("#"+one.f.flows.id.dashlet.datagrid.selectAllFlows) + .prop("checked", + true); + } else { + $("#"+one.f.flows.id.dashlet.datagrid.selectAllFlows) + .prop("checked", + false); + } + event.stopPropagation(); }); }); }); @@ -1193,6 +1243,11 @@ one.f.flows = { flowsDataGrid: function(data) { var source = new StaticDataSource({ columns: [ + { + property: 'selector', + label: "", + sortable: false + }, { property: 'name', label: 'Flow Name', @@ -1207,7 +1262,13 @@ one.f.flows = { data: data.flows, formatter: function(items) { $.each(items, function(index, item) { - item["name"] = '' + item["name"] + ''; }); @@ -1222,6 +1283,8 @@ one.f.flows = { $(data).each(function(index, value) { var tr = {}; var entry = []; + + entry.push(value['name']); entry.push(value['node']); if (value['flow']['installInHw'] == 'true' && value['flow']['status'] == 'Success') @@ -1244,12 +1307,12 @@ one.f.flows = { var $table = one.lib.dashlet.table.table(attributes); var headers = ['Flow Name', 'Node']; + var $thead = one.lib.dashlet.table.header(headers); $table.append($thead); var $tbody = one.lib.dashlet.table.body(body); $table.append($tbody); - return $table; } } diff --git a/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java b/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java index 337d721696..71ba687f2d 100644 --- a/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java +++ b/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java @@ -208,7 +208,7 @@ public class Troubleshoot implements IDaylightWeb { List statistics = statisticsManager .getNodeConnectorStatistics(node); for (NodeConnectorStatistics stats : statistics) { - cells.add(this.convertPortsStatistics(stats)); + cells.add(this.convertPortsStatistics(stats, containerName)); } } } @@ -220,11 +220,19 @@ public class Troubleshoot implements IDaylightWeb { } private Map convertPortsStatistics( - NodeConnectorStatistics ncStats) { + NodeConnectorStatistics ncStats, String containerName) { Map row = new HashMap(); + ISwitchManager switchManager = (ISwitchManager) ServiceHelper + .getInstance(ISwitchManager.class, containerName, this); + NodeConnector nodeConnector = ncStats.getNodeConnector(); + Description description = (Description) switchManager.getNodeProp(nodeConnector.getNode(), Description.propertyName); + String desc = (description == null) ? "" : description.getValue(); + String nodeName = desc.equalsIgnoreCase("none") ? nodeConnector.getNode().getNodeIDString() : desc; + String nodeConnectorDisplayName = nodeConnector.getType() + "|" + nodeConnector.getID() + "@" + nodeName; row.put("nodeConnector", - String.valueOf(ncStats.getNodeConnector().toString())); + String.valueOf(nodeConnectorDisplayName)); + row.put("rxPkts", String.valueOf(ncStats.getReceivePacketCount())); row.put("txPkts", String.valueOf(ncStats.getTransmitPacketCount())); row.put("rxBytes", String.valueOf(ncStats.getReceiveByteCount()));