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 if (context != null && context.getUserPrincipal() != null) 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>
170 *          <id>00:00:00:00:00:00:00:01</id>
171 *          <type>OF</type>
172 *       </node>
173 *       <ingressPort>1</ingressPort>
174 *       <priority>500</priority>
175 *       <etherType>0x800</etherType>
176 *       <nwSrc>9.9.1.1</nwSrc>
177 *       <actions>OUTPUT=2</actions>
178 *    </flowConfig>
182 * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
183 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
187 @Path("/{containerName}")
189 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
190 @TypeHint(FlowConfigs.class)
192 @ResponseCode(code = 200, condition = "Operation successful"),
193 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
194 @ResponseCode(code = 404, condition = "The containerName is not found"),
195 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
196 public FlowConfigs getStaticFlows(
197 @PathParam("containerName") String containerName) {
198 if (!NorthboundUtils.isAuthorized(
199 getUserName(), containerName, Privilege.READ, this)) {
200 throw new UnauthorizedException(
201 "User is not authorized to perform this operation on container "
205 List<FlowConfig> flowConfigs = getStaticFlowsInternal(containerName,
207 return new FlowConfigs(flowConfigs);
211 * Returns a list of Flows configured on a Node in a given container
213 * @param containerName
214 * Name of the Container (Eg. 'default')
216 * Type of the node being programmed (Eg. 'OF')
218 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
219 * @return List of flows configured on a Node in a container
226 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01
229 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
231 *    <flowConfig>
232 *       <installInHw>true</installInHw>
233 *       <name>flow1</name>
234 *       <node>
235 *          <id>00:00:00:00:00:00:00:01</id>
236 *          <type>OF</type>
237 *       </node>
238 *       <ingressPort>1</ingressPort>
239 *       <priority>500</priority>
240 *       <etherType>0x800</etherType>
241 *       <nwSrc>9.9.1.1</nwSrc>
242 *       <actions>OUTPUT=2</actions>
243 *    </flowConfig>
247 * {"flowConfig":{"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
248 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}}
252 @Path("/{containerName}/node/{nodeType}/{nodeId}")
254 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
255 @TypeHint(FlowConfigs.class)
257 @ResponseCode(code = 200, condition = "Operation successful"),
258 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
259 @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"),
260 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
261 public FlowConfigs getStaticFlows(
262 @PathParam("containerName") String containerName,
263 @PathParam("nodeType") String nodeType,
264 @PathParam("nodeId") String nodeId) {
265 if (!NorthboundUtils.isAuthorized(
266 getUserName(), containerName, Privilege.READ, this)) {
267 throw new UnauthorizedException(
268 "User is not authorized to perform this operation on container "
271 Node node = Node.fromString(nodeType, nodeId);
273 throw new ResourceNotFoundException(nodeId + " : "
274 + RestMessages.NONODE.toString());
276 List<FlowConfig> flows = getStaticFlowsInternal(containerName, node);
277 return new FlowConfigs(flows);
281 * Returns the flow configuration matching a human-readable name and nodeId
282 * on a given Container.
284 * @param containerName
285 * Name of the Container (Eg. 'default')
287 * Type of the node being programmed (Eg. 'OF')
289 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
291 * Human-readable name for the configured flow (Eg. 'Flow1')
292 * @return Flow configuration matching the name and nodeId on a Container
299 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
302 * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
304 *    <installInHw>true</installInHw>
305 *    <name>flow1</name>
306 *    <node>
307 *       <id>00:00:00:00:00:00:00:01</id>
308 *       <type>OF</type>
309 *    </node>
310 *    <ingressPort>1</ingressPort>
311 *    <priority>500</priority>
312 *    <etherType>0x800</etherType>
313 *    <nwSrc>9.9.1.1</nwSrc>
314 *    <actions>OUTPUT=2</actions>
315 * </flowConfig>
318 * {"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
319 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
323 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
325 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
326 @TypeHint(FlowConfig.class)
328 @ResponseCode(code = 200, condition = "Operation successful"),
329 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
330 @ResponseCode(code = 404, condition = "The containerName or NodeId or Configuration name is not found"),
331 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
332 public FlowConfig getStaticFlow(
333 @PathParam("containerName") String containerName,
334 @PathParam("nodeType") String nodeType,
335 @PathParam("nodeId") String nodeId, @PathParam("name") String name) {
336 if (!NorthboundUtils.isAuthorized(
337 getUserName(), containerName, Privilege.READ, this)) {
338 throw new UnauthorizedException(
339 "User is not authorized to perform this operation on container "
342 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
345 throw new ServiceUnavailableException("Flow Programmer "
346 + RestMessages.SERVICEUNAVAILABLE.toString());
349 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
351 FlowConfig staticFlow = frm.getStaticFlow(name, node);
352 if (staticFlow == null) {
353 throw new ResourceNotFoundException(RestMessages.NOFLOW.toString());
356 return new FlowConfig(staticFlow);
360 * Add a flow configuration
362 * @param containerName
363 * Name of the Container (Eg. 'default')
365 * Type of the node being programmed (Eg. 'OF')
367 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
369 * Name of the Static Flow configuration (Eg. 'Flow2')
371 * Flow Configuration in JSON or XML format
372 * @return Response as dictated by the HTTP Response Status code
379 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
383 *    <installInHw>true</installInHw>
384 *    <name>flow1</name>
385 *    <node>
386 *       <id>00:00:00:00:00:00:00:01</id>
387 *       <type>OF</type>
388 *    </node>
389 *    <ingressPort>1</ingressPort>
390 *    <priority>500</priority>
391 *    <etherType>0x800</etherType>
392 *    <nwSrc>9.9.1.1</nwSrc>
393 *    <actions>OUTPUT=2</actions>
394 * </flowConfig>
397 * {"installInHw":"true","name":"flow1","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},
398 * "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"9.9.1.1","actions":"OUTPUT=2"}
403 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
405 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
407 @ResponseCode(code = 201, condition = "Flow Config processed successfully"),
408 @ResponseCode(code = 400, condition = "Failed to create Static Flow entry due to invalid flow configuration"),
409 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
410 @ResponseCode(code = 404, condition = "The Container Name or nodeId is not found"),
411 @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
412 @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name or configuration"),
413 @ResponseCode(code = 500, condition = "Failed to create Static Flow entry. Failure Reason included in HTTP Error response"),
414 @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
415 public Response addFlow(
416 @PathParam(value = "containerName") String containerName,
417 @PathParam(value = "name") String name,
418 @PathParam("nodeType") String nodeType,
419 @PathParam(value = "nodeId") String nodeId,
420 @TypeHint(FlowConfig.class) JAXBElement<FlowConfig> flowConfig) {
422 if (!NorthboundUtils.isAuthorized(
423 getUserName(), containerName, Privilege.WRITE, this)) {
424 throw new UnauthorizedException(
425 "User is not authorized to perform this operation on container "
428 if (flowConfig.getValue().getNode() == null) {
429 return Response.status(Response.Status.BAD_REQUEST).entity("Invalid Configuration. Node is null or empty")
432 handleResourceCongruence(name, flowConfig.getValue().getName());
433 handleResourceCongruence(nodeId, flowConfig.getValue().getNode().getNodeIDString());
434 handleDefaultDisabled(containerName);
436 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
439 throw new ServiceUnavailableException("Flow Programmer "
440 + RestMessages.SERVICEUNAVAILABLE.toString());
443 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
445 FlowConfig staticFlow = frm.getStaticFlow(name, node);
446 if (staticFlow != null) {
447 throw new ResourceConflictException(name + " already exists."
448 + RestMessages.RESOURCECONFLICT.toString());
451 Status status = frm.addStaticFlow(flowConfig.getValue());
453 if (status.isSuccess()) {
454 NorthboundUtils.auditlog("Flow", username, "added", name, containerName);
455 return Response.status(Response.Status.CREATED).entity("Success").build();
457 return NorthboundUtils.getResponse(status);
461 * Delete a Flow configuration
463 * @param containerName
464 * Name of the Container (Eg. 'default')
466 * Type of the node being programmed (Eg. 'OF')
468 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
470 * Name of the Static Flow configuration (Eg. 'Flow1')
471 * @return Response as dictated by the HTTP Response code
478 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
483 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
485 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
487 @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
488 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
489 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
490 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
491 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
492 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
493 public Response deleteFlow(
494 @PathParam(value = "containerName") String containerName,
495 @PathParam(value = "name") String name,
496 @PathParam("nodeType") String nodeType,
497 @PathParam(value = "nodeId") String nodeId) {
499 if (!NorthboundUtils.isAuthorized(
500 getUserName(), containerName, Privilege.WRITE, this)) {
501 throw new UnauthorizedException(
502 "User is not authorized to perform this operation on container "
505 handleDefaultDisabled(containerName);
507 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
510 throw new ServiceUnavailableException("Flow Programmer "
511 + RestMessages.SERVICEUNAVAILABLE.toString());
514 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
516 FlowConfig staticFlow = frm.getStaticFlow(name, node);
517 if (staticFlow == null) {
518 throw new ResourceNotFoundException(name + " : "
519 + RestMessages.NOFLOW.toString());
522 Status status = frm.removeStaticFlow(name, node);
523 if (status.isSuccess()) {
524 NorthboundUtils.auditlog("Flow", username, "removed", name, containerName);
526 return NorthboundUtils.getResponse(status);
530 * Toggle a Flow configuration
532 * @param containerName
533 * Name of the Container (Eg. 'default')
535 * Type of the node being programmed (Eg. 'OF')
537 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
539 * Name of the Static Flow configuration (Eg. 'Flow1')
540 * @return Response as dictated by the HTTP Response code
547 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
551 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
553 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
555 @ResponseCode(code = 200, condition = "Flow Config processed successfully"),
556 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
557 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
558 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
559 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
560 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
561 public Response toggleFlow(
562 @PathParam(value = "containerName") String containerName,
563 @PathParam("nodeType") String nodeType,
564 @PathParam(value = "nodeId") String nodeId,
565 @PathParam(value = "name") String name) {
567 if (!NorthboundUtils.isAuthorized(
568 getUserName(), containerName, Privilege.WRITE, this)) {
569 throw new UnauthorizedException(
570 "User is not authorized to perform this operation on container "
574 handleDefaultDisabled(containerName);
576 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
579 throw new ServiceUnavailableException("Flow Programmer "
580 + RestMessages.SERVICEUNAVAILABLE.toString());
583 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
585 FlowConfig staticFlow = frm.getStaticFlow(name, node);
586 if (staticFlow == null) {
587 throw new ResourceNotFoundException(name + " : "
588 + RestMessages.NOFLOW.toString());
591 Status status = frm.toggleStaticFlowStatus(staticFlow);
592 if (status.isSuccess()) {
593 NorthboundUtils.auditlog("Flow", username, "toggled", name, containerName);
595 return NorthboundUtils.getResponse(status);
598 private Node handleNodeAvailability(String containerName, String nodeType,
601 Node node = Node.fromString(nodeType, nodeId);
603 throw new ResourceNotFoundException(nodeId + " : "
604 + RestMessages.NONODE.toString());
607 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
608 ISwitchManager.class, containerName, this);
611 throw new ServiceUnavailableException("Switch Manager "
612 + RestMessages.SERVICEUNAVAILABLE.toString());
615 if (!sm.getNodes().contains(node)) {
616 throw new ResourceNotFoundException(node.toString() + " : "
617 + RestMessages.NONODE.toString());
622 private void handleDefaultDisabled(String containerName) {
623 IContainerManager containerManager = (IContainerManager) ServiceHelper
624 .getGlobalInstance(IContainerManager.class, this);
625 if (containerManager == null) {
626 throw new InternalServerErrorException(
627 RestMessages.INTERNALERROR.toString());
629 if (containerName.equals(GlobalConstants.DEFAULT.toString())
630 && containerManager.hasNonDefaultContainer()) {
631 throw new NotAcceptableException(
632 RestMessages.DEFAULTDISABLED.toString());
636 private void handleResourceCongruence(String resource, String configured) {
637 if (!resource.equals(configured)) {
638 throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name");