X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fnorthbound%2Fflowprogrammer%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fflowprogrammer%2Fnorthbound%2FFlowProgrammerNorthbound.java;h=ea3f748dcc02e820c6b88aab5f2a10a73fef0cdf;hp=ed3abde0ab22bc84049f3f8787436d5a775a8fda;hb=171c283d513bc07c4422a37120b203b4bcc53e43;hpb=f5bc1a15da8600a9bc6dc7829792566cf0e72e7c diff --git a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java index ed3abde0ab..ea3f748dcc 100644 --- a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java +++ b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java @@ -1,4 +1,3 @@ - /* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * @@ -20,8 +19,10 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import javax.xml.bind.JAXBElement; import org.codehaus.enunciate.jaxrs.ResponseCode; @@ -32,10 +33,14 @@ import org.opendaylight.controller.forwardingrulesmanager.FlowConfig; import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager; import org.opendaylight.controller.northbound.commons.RestMessages; import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException; +import org.opendaylight.controller.northbound.commons.exception.MethodNotAllowedException; import org.opendaylight.controller.northbound.commons.exception.NotAcceptableException; import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException; import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; +import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; +import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Node; import org.opendaylight.controller.sal.utils.GlobalConstants; import org.opendaylight.controller.sal.utils.ServiceHelper; @@ -43,21 +48,31 @@ import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.switchmanager.ISwitchManager; /** - * Flow Configuration Northbound API + * Flow Configuration Northbound API provides capabilities to program flows. * - *

+ *
+ *
* Authentication scheme : HTTP Basic
* Authentication realm : opendaylight
* Transport : HTTP and HTTPS
*
- * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding - * a proper keystore / SSL certificate from a trusted authority.
- * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration + * HTTPS Authentication is disabled by default. * */ @Path("/") public class FlowProgrammerNorthbound { + private String username; + + @Context + public void setSecurityContext(SecurityContext context) { + if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName(); + } + + protected String getUserName() { + return username; + } + private IForwardingRulesManager getForwardingRulesManagerService( String containerName) { IContainerManager containerManager = (IContainerManager) ServiceHelper @@ -130,44 +145,125 @@ public class FlowProgrammerNorthbound { /** * Returns a list of Flows configured on the given container * - * @param containerName Name of the Container. The Container name for the base controller is "default". - * @return List of configured flows configured on a given container + * @param containerName + * Name of the Container (Eg. 'default') + * @return List of flows configured on a given container + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default
+     *
+     * Response in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+     * <list>
+     *        <flowConfig>
+     *               <installInHw>true</installInHw>
+     *               <name>flow1</name>
+     *               <node>
+     *                      <id>00:00:00:00:00:00:00:01</id>
+     *                      <type>OF</type>
+     *               </node>
+     *               <ingressPort>1</ingressPort>
+     *               <priority>500</priority>
+     *               <etherType>0x800</etherType>
+     *               <nwSrc>9.9.1.1</nwSrc>
+     *               <actions>OUTPUT=2</actions>
+     *        </flowConfig>
+     * </list>
+     *
+     * Response in JSON:
+     * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
+     * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
+     *
+     * 
*/ @Path("/{containerName}") @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(FlowConfigs.class) - @StatusCodes( { + @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 FlowConfigs getStaticFlows( @PathParam("containerName") String containerName) { - List flowConfigs = getStaticFlowsInternal(containerName, null); + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.READ, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } + + List flowConfigs = getStaticFlowsInternal(containerName, + null); return new FlowConfigs(flowConfigs); } /** * Returns a list of Flows configured on a Node in a given container * - * @param containerName Name of the Container. The Container name - * for the base controller is "default". - * @param nodeType Type of the node being programmed - * @param nodeId Node Identifier - * @return List of configured flows configured on a Node in a container + * @param containerName + * Name of the Container (Eg. 'default') + * @param nodeType + * Type of the node being programmed (Eg. 'OF') + * @param nodeId + * Node Identifier (Eg. '00:00:00:00:00:00:00:01') + * @return List of flows configured on a Node in a container + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01
+     *
+     * Response in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+     * <list>
+     *        <flowConfig>
+     *               <installInHw>true</installInHw>
+     *               <name>flow1</name>
+     *               <node>
+     *                      <id>00:00:00:00:00:00:00:01</id>
+     *                      <type>OF</type>
+     *               </node>
+     *               <ingressPort>1</ingressPort>
+     *               <priority>500</priority>
+     *               <etherType>0x800</etherType>
+     *               <nwSrc>9.9.1.1</nwSrc>
+     *               <actions>OUTPUT=2</actions>
+     *        </flowConfig>
+     * </list>
+     *
+     * Response in JSON:
+     * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
+     * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
+     *
+     * 
*/ - @Path("/{containerName}/{nodeType}/{nodeId}") + @Path("/{containerName}/node/{nodeType}/{nodeId}") @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(FlowConfigs.class) - @StatusCodes( { + @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public FlowConfigs getStaticFlows( @PathParam("containerName") String containerName, @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId) { + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.READ, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } Node node = Node.fromString(nodeType, nodeId); if (node == null) { throw new ResourceNotFoundException(nodeId + " : " @@ -178,29 +274,67 @@ public class FlowProgrammerNorthbound { } /** - * Returns the flow configuration matching a human-readable name and nodeId on a - * given Container. - * - * @param containerName Name of the Container. The Container name - * for the base controller is "default". - * @param nodeType Type of the node being programmed - * @param nodeId Node Identifier - * @param name Human-readable name for the configured flow. + * Returns the flow configuration matching a human-readable name and nodeId + * on a given Container. + * + * @param containerName + * Name of the Container (Eg. 'default') + * @param nodeType + * Type of the node being programmed (Eg. 'OF') + * @param nodeId + * Node Identifier (Eg. '00:00:00:00:00:00:00:01') + * @param name + * Human-readable name for the configured flow (Eg. 'Flow1') * @return Flow configuration matching the name and nodeId on a Container + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
+     *
+     * Response in XML:
+     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+     * <flowConfig>
+     *        <installInHw>true</installInHw>
+     *        <name>flow1</name>
+     *        <node>
+     *               <id>00:00:00:00:00:00:00:01</id>
+     *               <type>OF</type>
+     *        </node>
+     *        <ingressPort>1</ingressPort>
+     *        <priority>500</priority>
+     *        <etherType>0x800</etherType>
+     *        <nwSrc>9.9.1.1</nwSrc>
+     *        <actions>OUTPUT=2</actions>
+     * </flowConfig>
+     *
+     * Response in JSON:
+     * {"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
+     * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
+     *
+     * 
*/ - @Path("/{containerName}/{nodeType}/{nodeId}/{name}") + @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}") @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(FlowConfig.class) - @StatusCodes( { + @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName or NodeId or Configuration name is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public FlowConfig getStaticFlow( @PathParam("containerName") String containerName, @PathParam("nodeType") String nodeType, - @PathParam("nodeId") String nodeId, - @PathParam("name") String name) { + @PathParam("nodeId") String nodeId, @PathParam("name") String name) { + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.READ, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } IForwardingRulesManager frm = getForwardingRulesManagerService(containerName); if (frm == null) { @@ -219,25 +353,60 @@ public class FlowProgrammerNorthbound { } /** - * Add a flow configuration - * - * @param containerName Name of the Container. The Container name - * for the base controller is "default". - * @param nodeType Type of the node being programmed - * @param nodeId Node Identifier - * @param name Name of the Static Flow configuration - * @param FlowConfig Flow Configuration in JSON or XML format + * Add a flow configuration. If a flow by the given name already + * exists, this method will respond with a non-successful status response. + * + * @param containerName + * Name of the Container (Eg. 'default') + * @param nodeType + * Type of the node being programmed (Eg. 'OF') + * @param nodeId + * Node Identifier (Eg. '00:00:00:00:00:00:00:01') + * @param name + * Name of the Static Flow configuration (Eg. 'Flow2') + * @param FlowConfig + * Flow Configuration in JSON or XML format * @return Response as dictated by the HTTP Response Status code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
+     *
+     * Request in XML:
+     * <flowConfig>
+     *            <installInHw>true</installInHw>
+     *            <name>flow1</name>
+     *            <node>
+     *                   <id>00:00:00:00:00:00:00:01</id>
+     *                   <type>OF</type>
+     *            </node>
+     *            <ingressPort>1</ingressPort>
+     *            <priority>500</priority>
+     *            <etherType>0x800</etherType>
+     *            <nwSrc>9.9.1.1</nwSrc>
+     *            <actions>OUTPUT=2</actions>
+     * </flowConfig>
+     *
+     * Request in JSON:
+     * {"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
+     * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
+     *
+     * 
*/ - @Path("/{containerName}/{nodeType}/{nodeId}/{name}") - @POST - @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @StatusCodes( { + @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}") + @PUT + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @StatusCodes({ @ResponseCode(code = 201, condition = "Flow Config processed successfully"), - @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"), + @ResponseCode(code = 400, condition = "Failed to create Static Flow entry due to invalid flow configuration"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), + @ResponseCode(code = 404, condition = "The Container Name or nodeId is not found"), @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"), - @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name"), + @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name or configuration"), @ResponseCode(code = 500, condition = "Failed to create Static Flow entry. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") }) public Response addFlow( @@ -245,8 +414,21 @@ public class FlowProgrammerNorthbound { @PathParam(value = "name") String name, @PathParam("nodeType") String nodeType, @PathParam(value = "nodeId") String nodeId, - @TypeHint(FlowConfig.class) JAXBElement flowConfig) { + @TypeHint(FlowConfig.class) FlowConfig flowConfig) { + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.WRITE, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } + + if (flowConfig.getNode() == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("Invalid Configuration. Node is null or empty") + .build(); + } + handleResourceCongruence(name, flowConfig.getName()); + handleResourceCongruence(nodeId, flowConfig.getNode().getNodeIDString()); handleDefaultDisabled(containerName); IForwardingRulesManager frm = getForwardingRulesManagerService(containerName); @@ -264,33 +446,46 @@ public class FlowProgrammerNorthbound { + RestMessages.RESOURCECONFLICT.toString()); } - Status status = frm.addStaticFlow(flowConfig.getValue(), false); + Status status = frm.addStaticFlow(flowConfig); + if (status.isSuccess()) { - return Response.status(Response.Status.CREATED).build(); + NorthboundUtils.auditlog("Flow", username, "added", name, containerName); + return Response.status(Response.Status.CREATED).entity("Success").build(); } - throw new InternalServerErrorException(status.getDescription()); + return NorthboundUtils.getResponse(status); } /** * Delete a Flow configuration * - * DELETE /flows/{containerName}/{nodeType}/{nodeId}/{name} - * - * @param containerName Name of the Container. The Container name - * for the base controller is "default". - * @param nodeType Type of the node being programmed - * @param nodeId Node Identifier - * @param name Name of the Static Flow configuration + * @param containerName + * Name of the Container (Eg. 'default') + * @param nodeType + * Type of the node being programmed (Eg. 'OF') + * @param nodeId + * Node Identifier (Eg. '00:00:00:00:00:00:00:01') + * @param name + * Name of the Static Flow configuration (Eg. 'Flow1') * @return Response as dictated by the HTTP Response code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
+     *
+     * 
*/ - @Path("/{containerName}/{nodeType}/{nodeId}/{name}") + @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}") @DELETE - @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @StatusCodes( { - @ResponseCode(code = 200, condition = "Flow Config deleted successfully"), + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @StatusCodes({ + @ResponseCode(code = 204, condition = "Flow Config deleted successfully"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"), - @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"), + @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"), @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") }) public Response deleteFlow( @@ -299,6 +494,12 @@ public class FlowProgrammerNorthbound { @PathParam("nodeType") String nodeType, @PathParam(value = "nodeId") String nodeId) { + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.WRITE, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } handleDefaultDisabled(containerName); IForwardingRulesManager frm = getForwardingRulesManagerService(containerName); @@ -318,29 +519,42 @@ public class FlowProgrammerNorthbound { Status status = frm.removeStaticFlow(name, node); if (status.isSuccess()) { - return Response.ok().build(); + NorthboundUtils.auditlog("Flow", username, "removed", name, containerName); + return Response.noContent().build(); } - throw new InternalServerErrorException(status.getDescription()); + return NorthboundUtils.getResponse(status); } /** * Toggle a Flow configuration * - * @param containerName Name of the Container. The Container name - * for the base controller is "default". - * @param nodeType Type of the node being programmed - * @param nodeId Node Identifier - * @param name Name of the Static Flow configuration + * @param containerName + * Name of the Container (Eg. 'default') + * @param nodeType + * Type of the node being programmed (Eg. 'OF') + * @param nodeId + * Node Identifier (Eg. '00:00:00:00:00:00:00:01') + * @param name + * Name of the Static Flow configuration (Eg. 'Flow1') * @return Response as dictated by the HTTP Response code + * + *
+     *
+     * Example:
+     *
+     * RequestURL:
+     * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
+     *
+     * 
*/ - - @Path("/{containerName}/{nodeType}/{nodeId}/{name}") - @PUT - @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @StatusCodes( { - @ResponseCode(code = 200, condition = "Flow Config deleted successfully"), + @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}") + @POST + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @StatusCodes({ + @ResponseCode(code = 200, condition = "Flow Config processed successfully"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"), - @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"), + @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"), @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") }) public Response toggleFlow( @@ -349,6 +563,13 @@ public class FlowProgrammerNorthbound { @PathParam(value = "nodeId") String nodeId, @PathParam(value = "name") String name) { + if (!NorthboundUtils.isAuthorized( + getUserName(), containerName, Privilege.WRITE, this)) { + throw new UnauthorizedException( + "User is not authorized to perform this operation on container " + + containerName); + } + handleDefaultDisabled(containerName); IForwardingRulesManager frm = getForwardingRulesManagerService(containerName); @@ -368,13 +589,13 @@ public class FlowProgrammerNorthbound { Status status = frm.toggleStaticFlowStatus(staticFlow); if (status.isSuccess()) { - return Response.ok().build(); + NorthboundUtils.auditlog("Flow", username, "toggled", name, containerName); } - throw new InternalServerErrorException(status.getDescription()); + return NorthboundUtils.getResponse(status); } private Node handleNodeAvailability(String containerName, String nodeType, - String nodeId) { + String nodeId) { Node node = Node.fromString(nodeType, nodeId); if (node == null) { @@ -401,13 +622,19 @@ public class FlowProgrammerNorthbound { IContainerManager containerManager = (IContainerManager) ServiceHelper .getGlobalInstance(IContainerManager.class, this); if (containerManager == null) { - throw new InternalServerErrorException(RestMessages.INTERNALERROR - .toString()); + throw new InternalServerErrorException( + RestMessages.INTERNALERROR.toString()); } if (containerName.equals(GlobalConstants.DEFAULT.toString()) && containerManager.hasNonDefaultContainer()) { - throw new NotAcceptableException(RestMessages.DEFAULTDISABLED - .toString()); + throw new NotAcceptableException( + RestMessages.DEFAULTDISABLED.toString()); + } + } + + private void handleResourceCongruence(String resource, String configured) { + if (!resource.equals(configured)) { + throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name"); } }