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 handleResourceCongruence(name, flowConfig.getValue().getName());
429 handleResourceCongruence(nodeId, flowConfig.getValue().getNode().getNodeIDString());
430 handleDefaultDisabled(containerName);
432 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
435 throw new ServiceUnavailableException("Flow Programmer "
436 + RestMessages.SERVICEUNAVAILABLE.toString());
439 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
441 FlowConfig staticFlow = frm.getStaticFlow(name, node);
442 if (staticFlow != null) {
443 throw new ResourceConflictException(name + " already exists."
444 + RestMessages.RESOURCECONFLICT.toString());
447 Status status = frm.addStaticFlow(flowConfig.getValue());
449 if (status.isSuccess()) {
450 NorthboundUtils.auditlog("Flow", username, "added", name, containerName);
451 return Response.status(Response.Status.CREATED).entity("Success").build();
453 return NorthboundUtils.getResponse(status);
457 * Delete a Flow configuration
459 * @param containerName
460 * Name of the Container (Eg. 'default')
462 * Type of the node being programmed (Eg. 'OF')
464 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
466 * Name of the Static Flow configuration (Eg. 'Flow1')
467 * @return Response as dictated by the HTTP Response code
474 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
479 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
481 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
483 @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
484 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
485 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
486 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
487 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
488 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
489 public Response deleteFlow(
490 @PathParam(value = "containerName") String containerName,
491 @PathParam(value = "name") String name,
492 @PathParam("nodeType") String nodeType,
493 @PathParam(value = "nodeId") String nodeId) {
495 if (!NorthboundUtils.isAuthorized(
496 getUserName(), containerName, Privilege.WRITE, this)) {
497 throw new UnauthorizedException(
498 "User is not authorized to perform this operation on container "
501 handleDefaultDisabled(containerName);
503 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
506 throw new ServiceUnavailableException("Flow Programmer "
507 + RestMessages.SERVICEUNAVAILABLE.toString());
510 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
512 FlowConfig staticFlow = frm.getStaticFlow(name, node);
513 if (staticFlow == null) {
514 throw new ResourceNotFoundException(name + " : "
515 + RestMessages.NOFLOW.toString());
518 Status status = frm.removeStaticFlow(name, node);
519 if (status.isSuccess()) {
520 NorthboundUtils.auditlog("Flow", username, "removed", name, containerName);
522 return NorthboundUtils.getResponse(status);
526 * Toggle a Flow configuration
528 * @param containerName
529 * Name of the Container (Eg. 'default')
531 * Type of the node being programmed (Eg. 'OF')
533 * Node Identifier (Eg. '00:00:00:00:00:00:00:01')
535 * Name of the Static Flow configuration (Eg. 'Flow1')
536 * @return Response as dictated by the HTTP Response code
543 * http://localhost:8080/controller/nb/v2/flow/default/node/OF/00:00:00:00:00:00:00:01/static-flow/flow1
547 @Path("/{containerName}/node/{nodeType}/{nodeId}/static-flow/{name}")
549 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
551 @ResponseCode(code = 200, condition = "Flow Config processed successfully"),
552 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
553 @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
554 @ResponseCode(code = 406, condition = "Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response"),
555 @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
556 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
557 public Response toggleFlow(
558 @PathParam(value = "containerName") String containerName,
559 @PathParam("nodeType") String nodeType,
560 @PathParam(value = "nodeId") String nodeId,
561 @PathParam(value = "name") String name) {
563 if (!NorthboundUtils.isAuthorized(
564 getUserName(), containerName, Privilege.WRITE, this)) {
565 throw new UnauthorizedException(
566 "User is not authorized to perform this operation on container "
570 handleDefaultDisabled(containerName);
572 IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
575 throw new ServiceUnavailableException("Flow Programmer "
576 + RestMessages.SERVICEUNAVAILABLE.toString());
579 Node node = handleNodeAvailability(containerName, nodeType, nodeId);
581 FlowConfig staticFlow = frm.getStaticFlow(name, node);
582 if (staticFlow == null) {
583 throw new ResourceNotFoundException(name + " : "
584 + RestMessages.NOFLOW.toString());
587 Status status = frm.toggleStaticFlowStatus(staticFlow);
588 if (status.isSuccess()) {
589 NorthboundUtils.auditlog("Flow", username, "toggled", name, containerName);
591 return NorthboundUtils.getResponse(status);
594 private Node handleNodeAvailability(String containerName, String nodeType,
597 Node node = Node.fromString(nodeType, nodeId);
599 throw new ResourceNotFoundException(nodeId + " : "
600 + RestMessages.NONODE.toString());
603 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
604 ISwitchManager.class, containerName, this);
607 throw new ServiceUnavailableException("Switch Manager "
608 + RestMessages.SERVICEUNAVAILABLE.toString());
611 if (!sm.getNodes().contains(node)) {
612 throw new ResourceNotFoundException(node.toString() + " : "
613 + RestMessages.NONODE.toString());
618 private void handleDefaultDisabled(String containerName) {
619 IContainerManager containerManager = (IContainerManager) ServiceHelper
620 .getGlobalInstance(IContainerManager.class, this);
621 if (containerManager == null) {
622 throw new InternalServerErrorException(
623 RestMessages.INTERNALERROR.toString());
625 if (containerName.equals(GlobalConstants.DEFAULT.toString())
626 && containerManager.hasNonDefaultContainer()) {
627 throw new NotAcceptableException(
628 RestMessages.DEFAULTDISABLED.toString());
632 private void handleResourceCongruence(String resource, String configured) {
633 if (!resource.equals(configured)) {
634 throw new MethodNotAllowedException("Path's resource name conflicts with payload's resource name");