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;
26 import javax.xml.bind.JAXBElement;
28 import org.codehaus.enunciate.jaxrs.ResponseCode;
29 import org.codehaus.enunciate.jaxrs.StatusCodes;
30 import org.codehaus.enunciate.jaxrs.TypeHint;
31 import org.opendaylight.controller.containermanager.IContainerManager;
32 import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
33 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
34 import org.opendaylight.controller.northbound.commons.RestMessages;
35 import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
36 import org.opendaylight.controller.northbound.commons.exception.MethodNotAllowedException;
37 import org.opendaylight.controller.northbound.commons.exception.NotAcceptableException;
38 import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
39 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
40 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
41 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
42 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
43 import org.opendaylight.controller.sal.authorization.Privilege;
44 import org.opendaylight.controller.sal.core.Node;
45 import org.opendaylight.controller.sal.utils.GlobalConstants;
46 import org.opendaylight.controller.sal.utils.ServiceHelper;
47 import org.opendaylight.controller.sal.utils.Status;
48 import org.opendaylight.controller.switchmanager.ISwitchManager;
51 * Flow Configuration Northbound API
55 * Authentication scheme : <b>HTTP Basic</b><br>
56 * Authentication realm : <b>opendaylight</b><br>
57 * Transport : <b>HTTP and HTTPS</b><br>
59 * HTTPS Authentication is disabled by default. Administrator can enable it in
60 * tomcat-server.xml after adding a proper keystore / SSL certificate from a
61 * trusted authority.<br>
63 * http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
67 public class FlowProgrammerNorthbound {
69 private String username;
72 public void setSecurityContext(SecurityContext context) {
73 username = context.getUserPrincipal().getName();
76 protected String getUserName() {
80 private IForwardingRulesManager getForwardingRulesManagerService(
81 String containerName) {
82 IContainerManager containerManager = (IContainerManager) ServiceHelper
83 .getGlobalInstance(IContainerManager.class, this);
84 if (containerManager == null) {
85 throw new ServiceUnavailableException("Container "
86 + RestMessages.SERVICEUNAVAILABLE.toString());
89 boolean found = false;
90 List<String> containerNames = containerManager.getContainerNames();
91 for (String cName : containerNames) {
92 if (cName.trim().equalsIgnoreCase(containerName.trim())) {
98 throw new ResourceNotFoundException(containerName + " "
99 + RestMessages.NOCONTAINER.toString());
102 IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
103 .getInstance(IForwardingRulesManager.class, containerName, this);
106 throw new ServiceUnavailableException("Flow Programmer "
107 + RestMessages.SERVICEUNAVAILABLE.toString());
113 private List<FlowConfig> getStaticFlowsInternal(String containerName,
115 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
118 throw new ServiceUnavailableException("Flow Programmer "
119 + RestMessages.SERVICEUNAVAILABLE.toString());
122 List<FlowConfig> flows = new ArrayList<FlowConfig>();
125 for (FlowConfig flow : frm.getStaticFlows()) {
129 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
130 ISwitchManager.class, containerName, this);
133 throw new ServiceUnavailableException("Switch Manager "
134 + RestMessages.SERVICEUNAVAILABLE.toString());
137 if (!sm.getNodes().contains(node)) {
138 throw new ResourceNotFoundException(node.toString() + " : "
139 + RestMessages.NONODE.toString());
142 for (FlowConfig flow : frm.getStaticFlows(node)) {
150 * Returns a list of Flows configured on the given container
152 * @param containerName
153 * Name of the Container (Eg. 'default')
154 * @return List of flows configured on a given container
161 * http://localhost:8080/controller/nb/v2/flow/default
164 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
166 *    <flowConfig>
167 *       <installInHw>true</installInHw>
168 *       <name>flow1</name>
169 *       <node id="00:00:00:00:00:00:00:01" type="OF"/>
170 *       <ingressPort>1</ingressPort>
171 *       <priority>500</priority>
172 *       <etherType>0x800</etherType>
173 *       <nwSrc>9.9.1.1</nwSrc>
174 *       <actions>OUTPUT=2</actions>
175 *    </flowConfig>
179 * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"@id":"00:00:00:00:00:00:00:01","@type":"OF"},
180 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
184 @Path("/{containerName}")
186 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
187 @TypeHint(FlowConfigs.class)
189 @ResponseCode(code = 200, condition = "Operation successful"),
190 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
191 @ResponseCode(code = 404, condition = "The containerName is not found"),
192 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
193 public FlowConfigs getStaticFlows(
194 @PathParam("containerName") String containerName) {
195 if (!NorthboundUtils.isAuthorized(
196 getUserName(), containerName, Privilege.READ, this)) {
197 throw new UnauthorizedException(
198 "User is not authorized to perform this operation on container "
202 List<FlowConfig> flowConfigs = getStaticFlowsInternal(containerName,
204 return new FlowConfigs(flowConfigs);
208 * Returns a list of Flows configured on a Node in a given container
210 * @param containerName
211 * Name of the Container (Eg. 'default')
213 * Type of the node being programmed (Eg. 'OF')
215 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
216 * @return List of flows configured on a Node in a container
223 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01
226 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
228 *    <flowConfig>
229 *       <installInHw>true</installInHw>
230 *       <name>flow1</name>
231 *       <node id="00:00:00:00:00:00:00:01" type="OF"/>
232 *       <ingressPort>1</ingressPort>
233 *       <priority>500</priority>
234 *       <etherType>0x800</etherType>
235 *       <nwSrc>9.9.1.1</nwSrc>
236 *       <actions>OUTPUT=2</actions>
237 *    </flowConfig>
241 * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"@id":"00:00:00:00:00:00:00:01","@type":"OF"},
242 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
246 @Path("/{containerName}/node/{nodeType}/{nodeId}")
248 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
249 @TypeHint(FlowConfigs.class)
251 @ResponseCode(code = 200, condition = "Operation successful"),
252 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
253 @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"),
254 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
255 public FlowConfigs getStaticFlows(
256 @PathParam("containerName") String containerName,
257 @PathParam("nodeType") String nodeType,
258 @PathParam("nodeId") String nodeId) {
259 if (!NorthboundUtils.isAuthorized(
260 getUserName(), containerName, Privilege.READ, this)) {
261 throw new UnauthorizedException(
262 "User is not authorized to perform this operation on container "
265 Node node = Node.fromString(nodeType, nodeId);
267 throw new ResourceNotFoundException(nodeId + " : "
268 + RestMessages.NONODE.toString());
270 List<FlowConfig> flows = getStaticFlowsInternal(containerName, node);
271 return new FlowConfigs(flows);
275 * Returns the flow configuration matching a human-readable name and nodeId
276 * on a given Container.
278 * @param containerName
279 * Name of the Container (Eg. 'default')
281 * Type of the node being programmed (Eg. 'OF')
283 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
285 * Human-readable name for the configured flow (Eg. 'Flow1')
286 * @return Flow configuration matching the name and nodeId on a Container
293 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
296 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
298 *    <installInHw>true</installInHw>
299 *    <name>flow1</name>
300 *    <node id="00:00:00:00:00:00:00:01" type="OF"/>
301 *    <ingressPort>1</ingressPort>
302 *    <priority>500</priority>
303 *    <etherType>0x800</etherType>
304 *    <nwSrc>9.9.1.1</nwSrc>
305 *    <actions>OUTPUT=2</actions>
306 * </flowConfig>
309 * {"installInHw":"true","name":"flow1","node":{"@id":"00:00:00:00:00:00:00:01","@type":"OF"},
310 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
314 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
316 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
317 @TypeHint(FlowConfig.class)
319 @ResponseCode(code = 200, condition = "Operation successful"),
320 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
321 @ResponseCode(code = 404, condition = "The containerName or NodeId or Configuration name is not found"),
322 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
323 public FlowConfig getStaticFlow(
324 @PathParam("containerName") String containerName,
325 @PathParam("nodeType") String nodeType,
326 @PathParam("nodeId") String nodeId, @PathParam("name") String name) {
327 if (!NorthboundUtils.isAuthorized(
328 getUserName(), containerName, Privilege.READ, this)) {
329 throw new UnauthorizedException(
330 "User is not authorized to perform this operation on container "
333 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
336 throw new ServiceUnavailableException("Flow Programmer "
337 + RestMessages.SERVICEUNAVAILABLE.toString());
340 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
342 FlowConfig staticFlow = frm.getStaticFlow(name, node);
343 if (staticFlow == null) {
344 throw new ResourceNotFoundException(RestMessages.NOFLOW.toString());
347 return new FlowConfig(staticFlow);
351 * Add a flow configuration
353 * @param containerName
354 * Name of the Container (Eg. 'default')
356 * Type of the node being programmed (Eg. 'OF')
358 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
360 * Name of the Static Flow configuration (Eg. 'Flow2')
362 * Flow Configuration in JSON or XML format
363 * @return Response as dictated by the HTTP Response Status code
370 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
374 *    <installInHw>true</installInHw>
375 *    <name>flow1</name>
376 *    <node id="00:00:00:00:00:00:00:01" type="OF"/>
377 *    <ingressPort>1</ingressPort>
378 *    <priority>500</priority>
379 *    <etherType>0x800</etherType>
380 *    <nwSrc>9.9.1.1</nwSrc>
381 *    <actions>OUTPUT=2</actions>
382 * </flowConfig>
385 * {"installInHw":"true","name":"flow1","node":{"@id":"00:00:00:00:00:00:00:01","@type":"OF"},
386 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
391 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
393 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
395 @ResponseCode(code = 201, condition = "Flow Config processed successfully"),
396 @ResponseCode(code = 400, condition = "Failed to create Static Flow entry due to invalid flow configuration"),
397 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
398 @ResponseCode(code = 404, condition = "The Container Name or nodeId is not found"),
399 @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
400 @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name or configuration"),
401 @ResponseCode(code = 500, condition = "Failed to create Static Flow entry. Failure Reason included in HTTP Error response"),
402 @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
403 public Response addFlow(
404 @PathParam(value = "containerName") String containerName,
405 @PathParam(value = "name") String name,
406 @PathParam("nodeType") String nodeType,
407 @PathParam(value = "nodeId") String nodeId,
408 @TypeHint(FlowConfig.class) JAXBElement<FlowConfig> flowConfig) {
410 if (!NorthboundUtils.isAuthorized(
411 getUserName(), containerName, Privilege.WRITE, this)) {
412 throw new UnauthorizedException(
413 "User is not authorized to perform this operation on container "
416 handleResourceCongruence(name, flowConfig.getValue().getName());
417 handleResourceCongruence(nodeId, flowConfig.getValue().getNode().getNodeIDString());
418 handleDefaultDisabled(containerName);
420 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
423 throw new ServiceUnavailableException("Flow Programmer "
424 + RestMessages.SERVICEUNAVAILABLE.toString());
427 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
429 FlowConfig staticFlow = frm.getStaticFlow(name, node);
430 if (staticFlow != null) {
431 throw new ResourceConflictException(name + " already exists."
432 + RestMessages.RESOURCECONFLICT.toString());
435 Status status = frm.addStaticFlow(flowConfig.getValue());
437 if (status.isSuccess()) {
438 NorthboundUtils.auditlog("Flow", username, "added", name, containerName);
439 return Response.status(Response.Status.CREATED).entity("Success").build();
441 return NorthboundUtils.getResponse(status);
445 * Delete a Flow configuration
447 * @param containerName
448 * Name of the Container (Eg. 'default')
450 * Type of the node being programmed (Eg. 'OF')
452 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
454 * Name of the Static Flow configuration (Eg. 'Flow1')
455 * @return Response as dictated by the HTTP Response code
462 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
467 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
469 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
471 @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
472 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
473 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
474 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
475 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
476 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
477 public Response deleteFlow(
478 @PathParam(value = "containerName") String containerName,
479 @PathParam(value = "name") String name,
480 @PathParam("nodeType") String nodeType,
481 @PathParam(value = "nodeId") String nodeId) {
483 if (!NorthboundUtils.isAuthorized(
484 getUserName(), containerName, Privilege.WRITE, this)) {
485 throw new UnauthorizedException(
486 "User is not authorized to perform this operation on container "
489 handleDefaultDisabled(containerName);
491 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
494 throw new ServiceUnavailableException("Flow Programmer "
495 + RestMessages.SERVICEUNAVAILABLE.toString());
498 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
500 FlowConfig staticFlow = frm.getStaticFlow(name, node);
501 if (staticFlow == null) {
502 throw new ResourceNotFoundException(name + " : "
503 + RestMessages.NOFLOW.toString());
506 Status status = frm.removeStaticFlow(name, node);
507 if (status.isSuccess()) {
508 NorthboundUtils.auditlog("Flow", username, "removed", name, containerName);
510 return NorthboundUtils.getResponse(status);
514 * Toggle a Flow configuration
516 * @param containerName
517 * Name of the Container (Eg. 'default')
519 * Type of the node being programmed (Eg. 'OF')
521 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
523 * Name of the Static Flow configuration (Eg. 'Flow1')
524 * @return Response as dictated by the HTTP Response code
531 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
535 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
537 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
539 @ResponseCode(code = 200, condition = "Flow Config processed successfully"),
540 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
541 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
542 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
543 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
544 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
545 public Response toggleFlow(
546 @PathParam(value = "containerName") String containerName,
547 @PathParam("nodeType") String nodeType,
548 @PathParam(value = "nodeId") String nodeId,
549 @PathParam(value = "name") String name) {
551 if (!NorthboundUtils.isAuthorized(
552 getUserName(), containerName, Privilege.WRITE, this)) {
553 throw new UnauthorizedException(
554 "User is not authorized to perform this operation on container "
558 handleDefaultDisabled(containerName);
560 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
563 throw new ServiceUnavailableException("Flow Programmer "
564 + RestMessages.SERVICEUNAVAILABLE.toString());
567 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
569 FlowConfig staticFlow = frm.getStaticFlow(name, node);
570 if (staticFlow == null) {
571 throw new ResourceNotFoundException(name + " : "
572 + RestMessages.NOFLOW.toString());
575 Status status = frm.toggleStaticFlowStatus(staticFlow);
576 if (status.isSuccess()) {
577 NorthboundUtils.auditlog("Flow", username, "toggled", name, containerName);
579 return NorthboundUtils.getResponse(status);
582 private Node handleNodeAvailability(String containerName, String nodeType,
585 Node node = Node.fromString(nodeType, nodeId);
587 throw new ResourceNotFoundException(nodeId + " : "
588 + RestMessages.NONODE.toString());
591 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
592 ISwitchManager.class, containerName, this);
595 throw new ServiceUnavailableException("Switch Manager "
596 + RestMessages.SERVICEUNAVAILABLE.toString());
599 if (!sm.getNodes().contains(node)) {
600 throw new ResourceNotFoundException(node.toString() + " : "
601 + RestMessages.NONODE.toString());
606 private void handleDefaultDisabled(String containerName) {
607 IContainerManager containerManager = (IContainerManager) ServiceHelper
608 .getGlobalInstance(IContainerManager.class, this);
609 if (containerManager == null) {
610 throw new InternalServerErrorException(
611 RestMessages.INTERNALERROR.toString());
613 if (containerName.equals(GlobalConstants.DEFAULT.toString())
614 && containerManager.hasNonDefaultContainer()) {
615 throw new NotAcceptableException(
616 RestMessages.DEFAULTDISABLED.toString());
620 private void handleResourceCongruence(String resource, String configured) {
621 if (!resource.equals(configured)) {
622 throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name");