2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.flowprogrammer.northbound;
11 import java.util.ArrayList;
12 import java.util.List;
14 import javax.ws.rs.Consumes;
15 import javax.ws.rs.DELETE;
16 import javax.ws.rs.GET;
17 import javax.ws.rs.POST;
18 import javax.ws.rs.PUT;
19 import javax.ws.rs.Path;
20 import javax.ws.rs.PathParam;
21 import javax.ws.rs.Produces;
22 import javax.ws.rs.core.Context;
23 import javax.ws.rs.core.MediaType;
24 import javax.ws.rs.core.Response;
25 import javax.ws.rs.core.SecurityContext;
27 import org.codehaus.enunciate.jaxrs.ResponseCode;
28 import org.codehaus.enunciate.jaxrs.StatusCodes;
29 import org.codehaus.enunciate.jaxrs.TypeHint;
30 import org.opendaylight.controller.containermanager.IContainerManager;
31 import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
32 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
33 import org.opendaylight.controller.northbound.commons.RestMessages;
34 import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
35 import org.opendaylight.controller.northbound.commons.exception.MethodNotAllowedException;
36 import org.opendaylight.controller.northbound.commons.exception.NotAcceptableException;
37 import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
38 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
39 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
40 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
41 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
42 import org.opendaylight.controller.sal.authorization.Privilege;
43 import org.opendaylight.controller.sal.core.Node;
44 import org.opendaylight.controller.sal.utils.GlobalConstants;
45 import org.opendaylight.controller.sal.utils.ServiceHelper;
46 import org.opendaylight.controller.sal.utils.Status;
47 import org.opendaylight.controller.switchmanager.ISwitchManager;
50 * Flow Configuration Northbound API provides capabilities to program flows.
54 * Authentication scheme : <b>HTTP Basic</b><br>
55 * Authentication realm : <b>opendaylight</b><br>
56 * Transport : <b>HTTP and HTTPS</b><br>
58 * HTTPS Authentication is disabled by default.
62 public class FlowProgrammerNorthbound {
64 private String username;
67 public void setSecurityContext(SecurityContext context) {
68 if (context != null && context.getUserPrincipal() != null) {
69 username = context.getUserPrincipal().getName();
73 protected String getUserName() {
77 private IForwardingRulesManager getForwardingRulesManagerService(String containerName) {
78 IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
79 IContainerManager.class, this);
80 if (containerManager == null) {
81 throw new ServiceUnavailableException("Container " + RestMessages.SERVICEUNAVAILABLE.toString());
84 boolean found = false;
85 List<String> containerNames = containerManager.getContainerNames();
86 for (String cName : containerNames) {
87 if (cName.trim().equalsIgnoreCase(containerName.trim())) {
93 throw new ResourceNotFoundException(containerName + " " + RestMessages.NOCONTAINER.toString());
96 IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper.getInstance(
97 IForwardingRulesManager.class, containerName, this);
100 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
106 private List<FlowConfig> getStaticFlowsInternal(String containerName, Node node) {
107 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
110 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
113 List<FlowConfig> flows = new ArrayList<FlowConfig>();
116 for (FlowConfig flow : frm.getStaticFlows()) {
120 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
123 throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
126 if (!sm.getNodes().contains(node)) {
127 throw new ResourceNotFoundException(node.toString() + " : " + RestMessages.NONODE.toString());
130 for (FlowConfig flow : frm.getStaticFlows(node)) {
138 * Returns a list of Flows configured on the given container
140 * @param containerName
141 * Name of the Container (Eg. 'default')
142 * @return List of flows configured on a given container
149 * http://localhost:8080/controller/nb/v2/flowprogrammer/default
151 * Response body in XML:
152 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
154 *    <flowConfig>
155 *       <installInHw>true</installInHw>
156 *       <name>flow1</name>
157 *       <node>
158 *          <id>00:00:00:00:00:00:00:01</id>
159 *          <type>OF</type>
160 *       </node>
161 *       <ingressPort>1</ingressPort>
162 *       <priority>500</priority>
163 *       <etherType>0x800</etherType>
164 *       <nwSrc>9.9.1.1</nwSrc>
165 *       <actions>OUTPUT=2</actions>
166 *    </flowConfig>
169 * Response body in JSON:
173 * "installInHw": "true",
177 * "id": "00:00:00:00:00:00:00:01"
179 * "ingressPort": "1",
181 * "etherType": "0x800",
191 @Path("/{containerName}")
193 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
194 @TypeHint(FlowConfigs.class)
195 @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
196 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
197 @ResponseCode(code = 404, condition = "The containerName is not found"),
198 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
199 public FlowConfigs getStaticFlows(@PathParam("containerName") String containerName) {
200 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
201 throw new UnauthorizedException("User is not authorized to perform this operation on container "
205 List<FlowConfig> flowConfigs = getStaticFlowsInternal(containerName, null);
206 return new FlowConfigs(flowConfigs);
210 * Returns a list of Flows configured on a Node in a given container
212 * @param containerName
213 * Name of the Container (Eg. 'default')
215 * Type of the node being programmed (Eg. 'OF')
217 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
218 * @return List of flows configured on a Node in a container
225 * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01
227 * Response body in XML:
228 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
230 *    <flowConfig>
231 *       <installInHw>true</installInHw>
232 *       <name>flow1</name>
233 *       <node>
234 *          <id>00:00:00:00:00:00:00:01</id>
235 *          <type>OF</type>
236 *       </node>
237 *       <ingressPort>1</ingressPort>
238 *       <priority>500</priority>
239 *       <etherType>0x800</etherType>
240 *       <nwSrc>9.9.1.1</nwSrc>
241 *       <actions>OUTPUT=2</actions>
242 *    </flowConfig>
245 * Response body in JSON:
249 * "installInHw": "true",
253 * "id": "00:00:00:00:00:00:00:01"
255 * "ingressPort": "1",
257 * "etherType": "0x800",
267 @Path("/{containerName}/node/{nodeType}/{nodeId}")
269 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
270 @TypeHint(FlowConfigs.class)
271 @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
272 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
273 @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"),
274 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
275 public FlowConfigs getStaticFlows(@PathParam("containerName") String containerName,
276 @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId) {
277 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
278 throw new UnauthorizedException("User is not authorized to perform this operation on container "
281 Node node = Node.fromString(nodeType, nodeId);
283 throw new ResourceNotFoundException(nodeId + " : " + RestMessages.NONODE.toString());
285 List<FlowConfig> flows = getStaticFlowsInternal(containerName, node);
286 return new FlowConfigs(flows);
290 * Returns the flow configuration matching a human-readable name and nodeId
291 * on a given Container.
293 * @param containerName
294 * Name of the Container (Eg. 'default')
296 * Type of the node being programmed (Eg. 'OF')
298 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
300 * Human-readable name for the configured flow (Eg. 'Flow1')
301 * @return Flow configuration matching the name and nodeId on a Container
308 * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
310 * Response body in XML:
311 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
313 *    <installInHw>true</installInHw>
314 *    <name>flow1</name>
315 *    <node>
316 *       <id>00:00:00:00:00:00:00:01</id>
317 *       <type>OF</type>
318 *    </node>
319 *    <ingressPort>1</ingressPort>
320 *    <priority>500</priority>
321 *    <etherType>0x800</etherType>
322 *    <nwSrc>9.9.1.1</nwSrc>
323 *    <actions>OUTPUT=2</actions>
324 * </flowConfig>
326 * Response body in JSON:
328 * "installInHw":"true",
331 * "id":"00:00:00:00:00:00:00:01",
336 * "etherType":"0x800",
345 @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}")
347 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
348 @TypeHint(FlowConfig.class)
349 @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
350 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
351 @ResponseCode(code = 404, condition = "The containerName or NodeId or Configuration name is not found"),
352 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
353 public FlowConfig getStaticFlow(@PathParam("containerName") String containerName,
354 @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId, @PathParam("name") String name) {
355 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
356 throw new UnauthorizedException("User is not authorized to perform this operation on container "
359 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
362 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
365 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
367 FlowConfig staticFlow = frm.getStaticFlow(name, node);
368 if (staticFlow == null) {
369 throw new ResourceNotFoundException(RestMessages.NOFLOW.toString());
372 return new FlowConfig(staticFlow);
376 * Add a flow configuration. If a flow by the given name already exists,
377 * this method will respond with a non-successful status response.
379 * @param containerName
380 * Name of the Container (Eg. 'default')
382 * Type of the node being programmed (Eg. 'OF')
384 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
386 * Name of the Static Flow configuration (Eg. 'Flow2')
388 * Flow Configuration in JSON or XML format
389 * @return Response as dictated by the HTTP Response Status code
396 * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
398 * Request body in XML:
400 *    <installInHw>true</installInHw>
401 *    <name>flow1</name>
402 *    <node>
403 *       <id>00:00:00:00:00:00:00:01</id>
404 *       <type>OF</type>
405 *    </node>
406 *    <ingressPort>1</ingressPort>
407 *    <priority>500</priority>
408 *    <etherType>0x800</etherType>
409 *    <nwSrc>9.9.1.1</nwSrc>
410 *    <actions>OUTPUT=2</actions>
411 * </flowConfig>
413 * Request body in JSON:
415 * "installInHw":"true",
418 * "id":"00:00:00:00:00:00:00:01",
423 * "etherType":"0x800",
432 @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}")
434 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
436 @ResponseCode(code = 201, condition = "Flow Config processed successfully"),
437 @ResponseCode(code = 400, condition = "Failed to create Static Flow entry due to invalid flow configuration"),
438 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
439 @ResponseCode(code = 404, condition = "The Container Name or nodeId is not found"),
440 @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
441 @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name or configuration"),
442 @ResponseCode(code = 500, condition = "Failed to create Static Flow entry. Failure Reason included in HTTP Error response"),
443 @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
444 public Response addFlow(@PathParam(value = "containerName") String containerName,
445 @PathParam(value = "name") String name, @PathParam("nodeType") String nodeType,
446 @PathParam(value = "nodeId") String nodeId, @TypeHint(FlowConfig.class) FlowConfig flowConfig) {
448 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
449 throw new UnauthorizedException("User is not authorized to perform this operation on container "
453 if (flowConfig.getNode() == null) {
454 return Response.status(Response.Status.BAD_REQUEST).entity("Invalid Configuration. Node is null or empty")
457 handleResourceCongruence(name, flowConfig.getName());
458 handleResourceCongruence(nodeId, flowConfig.getNode().getNodeIDString());
459 handleDefaultDisabled(containerName);
461 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
464 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
467 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
469 FlowConfig staticFlow = frm.getStaticFlow(name, node);
470 if (staticFlow != null) {
471 throw new ResourceConflictException(name + " already exists." + RestMessages.RESOURCECONFLICT.toString());
474 Status status = frm.addStaticFlow(flowConfig);
476 if (status.isSuccess()) {
477 NorthboundUtils.auditlog("Flow Entry", username, "added",
478 name + " on Node " + NorthboundUtils.getNodeDesc(node, containerName, this), containerName);
479 return Response.status(Response.Status.CREATED).entity("Success").build();
481 return NorthboundUtils.getResponse(status);
485 * Delete a Flow configuration
487 * @param containerName
488 * Name of the Container (Eg. 'default')
490 * Type of the node being programmed (Eg. 'OF')
492 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
494 * Name of the Static Flow configuration (Eg. 'Flow1')
495 * @return Response as dictated by the HTTP Response code
502 * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
507 @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}")
509 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
511 @ResponseCode(code = 204, condition = "Flow Config deleted successfully"),
512 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
513 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
514 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
515 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
516 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
517 public Response deleteFlow(@PathParam(value = "containerName") String containerName,
518 @PathParam(value = "name") String name, @PathParam("nodeType") String nodeType,
519 @PathParam(value = "nodeId") String nodeId) {
521 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
522 throw new UnauthorizedException("User is not authorized to perform this operation on container "
525 handleDefaultDisabled(containerName);
527 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
530 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
533 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
535 FlowConfig staticFlow = frm.getStaticFlow(name, node);
536 if (staticFlow == null) {
537 throw new ResourceNotFoundException(name + " : " + RestMessages.NOFLOW.toString());
540 Status status = frm.removeStaticFlow(name, node);
541 if (status.isSuccess()) {
542 NorthboundUtils.auditlog("Flow Entry", username, "removed",
543 name + " from Node " + NorthboundUtils.getNodeDesc(node, containerName, this), containerName);
544 return Response.noContent().build();
546 return NorthboundUtils.getResponse(status);
550 * Toggle a Flow configuration
552 * @param containerName
553 * Name of the Container (Eg. 'default')
555 * Type of the node being programmed (Eg. 'OF')
557 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
559 * Name of the Static Flow configuration (Eg. 'Flow1')
560 * @return Response as dictated by the HTTP Response code
567 * http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1
571 @Path("/{containerName}/node/{nodeType}/{nodeId}/staticFlow/{name}")
573 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
575 @ResponseCode(code = 200, condition = "Flow Config processed successfully"),
576 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
577 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
578 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
579 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
580 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
581 public Response toggleFlow(@PathParam(value = "containerName") String containerName,
582 @PathParam("nodeType") String nodeType, @PathParam(value = "nodeId") String nodeId,
583 @PathParam(value = "name") String name) {
585 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
586 throw new UnauthorizedException("User is not authorized to perform this operation on container "
590 handleDefaultDisabled(containerName);
592 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
595 throw new ServiceUnavailableException("Flow Programmer " + RestMessages.SERVICEUNAVAILABLE.toString());
598 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
600 FlowConfig staticFlow = frm.getStaticFlow(name, node);
601 if (staticFlow == null) {
602 throw new ResourceNotFoundException(name + " : " + RestMessages.NOFLOW.toString());
605 Status status = frm.toggleStaticFlowStatus(staticFlow);
606 if (status.isSuccess()) {
607 NorthboundUtils.auditlog("Flow Entry", username, "toggled",
608 name + " on Node " + NorthboundUtils.getNodeDesc(node, containerName, this), containerName);
610 return NorthboundUtils.getResponse(status);
613 private Node handleNodeAvailability(String containerName, String nodeType, String nodeId) {
615 Node node = Node.fromString(nodeType, nodeId);
617 throw new ResourceNotFoundException(nodeId + " : " + RestMessages.NONODE.toString());
620 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
623 throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
626 if (!sm.getNodes().contains(node)) {
627 throw new ResourceNotFoundException(node.toString() + " : " + RestMessages.NONODE.toString());
632 private void handleDefaultDisabled(String containerName) {
633 IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
634 IContainerManager.class, this);
635 if (containerManager == null) {
636 throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());
638 if (containerName.equals(GlobalConstants.DEFAULT.toString()) && containerManager.hasNonDefaultContainer()) {
639 throw new NotAcceptableException(RestMessages.DEFAULTDISABLED.toString());
643 private void handleResourceCongruence(String resource, String configured) {
644 if (!resource.equals(configured)) {
645 throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name");