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) 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 "
429 if (flowConfig.getNode() == null) {
430 return Response.status(Response.Status.BAD_REQUEST).entity("Invalid Configuration. Node is null or empty")
433 handleResourceCongruence(name, flowConfig.getName());
434 handleResourceCongruence(nodeId, flowConfig.getNode().getNodeIDString());
435 handleDefaultDisabled(containerName);
437 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
440 throw new ServiceUnavailableException("Flow Programmer "
441 + RestMessages.SERVICEUNAVAILABLE.toString());
444 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
446 FlowConfig staticFlow = frm.getStaticFlow(name, node);
447 if (staticFlow != null) {
448 throw new ResourceConflictException(name + " already exists."
449 + RestMessages.RESOURCECONFLICT.toString());
452 Status status = frm.addStaticFlow(flowConfig);
454 if (status.isSuccess()) {
455 NorthboundUtils.auditlog("Flow", username, "added", name, containerName);
456 return Response.status(Response.Status.CREATED).entity("Success").build();
458 return NorthboundUtils.getResponse(status);
462 * Delete a Flow configuration
464 * @param containerName
465 * Name of the Container (Eg. 'default')
467 * Type of the node being programmed (Eg. 'OF')
469 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
471 * Name of the Static Flow configuration (Eg. 'Flow1')
472 * @return Response as dictated by the HTTP Response code
479 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
484 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
486 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
488 @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
489 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
490 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
491 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
492 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
493 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
494 public Response deleteFlow(
495 @PathParam(value = "containerName") String containerName,
496 @PathParam(value = "name") String name,
497 @PathParam("nodeType") String nodeType,
498 @PathParam(value = "nodeId") String nodeId) {
500 if (!NorthboundUtils.isAuthorized(
501 getUserName(), containerName, Privilege.WRITE, this)) {
502 throw new UnauthorizedException(
503 "User is not authorized to perform this operation on container "
506 handleDefaultDisabled(containerName);
508 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
511 throw new ServiceUnavailableException("Flow Programmer "
512 + RestMessages.SERVICEUNAVAILABLE.toString());
515 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
517 FlowConfig staticFlow = frm.getStaticFlow(name, node);
518 if (staticFlow == null) {
519 throw new ResourceNotFoundException(name + " : "
520 + RestMessages.NOFLOW.toString());
523 Status status = frm.removeStaticFlow(name, node);
524 if (status.isSuccess()) {
525 NorthboundUtils.auditlog("Flow", username, "removed", name, containerName);
527 return NorthboundUtils.getResponse(status);
531 * Toggle a Flow configuration
533 * @param containerName
534 * Name of the Container (Eg. 'default')
536 * Type of the node being programmed (Eg. 'OF')
538 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
540 * Name of the Static Flow configuration (Eg. 'Flow1')
541 * @return Response as dictated by the HTTP Response code
548 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
552 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
554 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
556 @ResponseCode(code = 200, condition = "Flow Config processed successfully"),
557 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
558 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
559 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
560 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
561 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
562 public Response toggleFlow(
563 @PathParam(value = "containerName") String containerName,
564 @PathParam("nodeType") String nodeType,
565 @PathParam(value = "nodeId") String nodeId,
566 @PathParam(value = "name") String name) {
568 if (!NorthboundUtils.isAuthorized(
569 getUserName(), containerName, Privilege.WRITE, this)) {
570 throw new UnauthorizedException(
571 "User is not authorized to perform this operation on container "
575 handleDefaultDisabled(containerName);
577 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
580 throw new ServiceUnavailableException("Flow Programmer "
581 + RestMessages.SERVICEUNAVAILABLE.toString());
584 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
586 FlowConfig staticFlow = frm.getStaticFlow(name, node);
587 if (staticFlow == null) {
588 throw new ResourceNotFoundException(name + " : "
589 + RestMessages.NOFLOW.toString());
592 Status status = frm.toggleStaticFlowStatus(staticFlow);
593 if (status.isSuccess()) {
594 NorthboundUtils.auditlog("Flow", username, "toggled", name, containerName);
596 return NorthboundUtils.getResponse(status);
599 private Node handleNodeAvailability(String containerName, String nodeType,
602 Node node = Node.fromString(nodeType, nodeId);
604 throw new ResourceNotFoundException(nodeId + " : "
605 + RestMessages.NONODE.toString());
608 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
609 ISwitchManager.class, containerName, this);
612 throw new ServiceUnavailableException("Switch Manager "
613 + RestMessages.SERVICEUNAVAILABLE.toString());
616 if (!sm.getNodes().contains(node)) {
617 throw new ResourceNotFoundException(node.toString() + " : "
618 + RestMessages.NONODE.toString());
623 private void handleDefaultDisabled(String containerName) {
624 IContainerManager containerManager = (IContainerManager) ServiceHelper
625 .getGlobalInstance(IContainerManager.class, this);
626 if (containerManager == null) {
627 throw new InternalServerErrorException(
628 RestMessages.INTERNALERROR.toString());
630 if (containerName.equals(GlobalConstants.DEFAULT.toString())
631 && containerManager.hasNonDefaultContainer()) {
632 throw new NotAcceptableException(
633 RestMessages.DEFAULTDISABLED.toString());
637 private void handleResourceCongruence(String resource, String configured) {
638 if (!resource.equals(configured)) {
639 throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name");