/*
* Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.controller.containermanager.northbound;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import org.codehaus.enunciate.jaxrs.ResponseCode;
import org.codehaus.enunciate.jaxrs.StatusCodes;
import org.codehaus.enunciate.jaxrs.TypeHint;
import org.opendaylight.controller.containermanager.ContainerConfig;
import org.opendaylight.controller.containermanager.ContainerFlowConfig;
import org.opendaylight.controller.containermanager.IContainerAuthorization;
import org.opendaylight.controller.containermanager.IContainerManager;
import org.opendaylight.controller.northbound.commons.RestMessages;
import org.opendaylight.controller.northbound.commons.exception.BadRequestException;
import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
import org.opendaylight.controller.northbound.commons.exception.ResourceForbiddenException;
import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
import org.opendaylight.controller.sal.authorization.Privilege;
import org.opendaylight.controller.sal.authorization.UserLevel;
import org.opendaylight.controller.sal.utils.GlobalConstants;
import org.opendaylight.controller.sal.utils.ServiceHelper;
import org.opendaylight.controller.sal.utils.Status;
import org.opendaylight.controller.usermanager.IUserManager;
/**
* Container Manager Northbound API
*
*
*
* Authentication scheme : HTTP Basic
* Authentication realm : opendaylight
* Transport : HTTP and HTTPS
*
* HTTPS Authentication is disabled by default. Administrator can enable it in
* tomcat-server.xml after adding a proper keystore / SSL certificate from a
* trusted authority.
* More info :
* http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
*
*/
@Path("/")
public class ContainerManagerNorthbound {
private String username;
@Context
public void setSecurityContext(SecurityContext context) {
if (context != null && context.getUserPrincipal() != null) {
username = context.getUserPrincipal().getName();
}
}
protected String getUserName() {
return username;
}
private IContainerManager getContainerManager() {
IContainerManager containerMgr = (IContainerManager) ServiceHelper.getGlobalInstance(IContainerManager.class, this);
if (containerMgr == null) {
throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());
}
return containerMgr;
}
private void handleNameMismatch(String name, String nameinURL) {
if (name == null || nameinURL == null) {
throw new BadRequestException(RestMessages.INVALIDJSON.toString());
}
if (name.equalsIgnoreCase(nameinURL)) {
return;
}
throw new BadRequestException(RestMessages.INVALIDJSON.toString());
}
/**
* Get all the containers configured in the system
*
* @return a List of all {@link org.opendaylight.controller.containermanager.ContainerConfig}
*
*
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/containers * * Response body in XML: * <container-config-list> * <container-config> * <container>black</container> * <staticVlan>10</staticVlan> * <nodeConnectors>OF|1@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|23@OF|00:00:00:00:00:00:20:21</nodeConnectors> * <flowSpecs> * <name>tcp</name> * <protocol>TCP</protocol> * </flowSpecs> * </container-config> * <container-config> * <container>red</container> * <staticVlan>20</staticVlan> * <nodeConnectors>OF|1@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|23@OF|00:00:00:00:00:00:20:21</nodeConnectors> * <flowSpecs> * <name>udp</name> * <protocol>UDP</protocol> * </flowSpecs> * </container-config> * </container-config-list> * * Response body in JSON: * { "container-config" : [ * { "container" : "black", * "nodeConnectors" : [ * "OF|1@OF|00:00:00:00:00:00:00:01", "OF|23@OF|00:00:00:00:00:00:20:21" * ], * "staticVlan" : "10", * "flowSpecs : [ * { "name": "udp", * "protocol": "UDP" } * ] * }, * { "container" : "red", * "nodeConnectors" : [ * "OF|1@OF|00:00:00:00:00:00:00:01", * "OF|23@OF|00:00:00:00:00:00:20:21" * ], * "staticVlan" : "20", * "flowSpecs": [ * { "name": "tcp", * "protocol": "TCP" * } * ] * } * ] * } **/ @Path("/containers") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(ContainerConfigs.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User is not authorized to perform this operation"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public ContainerConfigs viewAllContainers() { handleNetworkAuthorization(getUserName()); IContainerManager containerManager = getContainerManager(); return new ContainerConfigs(containerManager.getContainerConfigList()); } /** * Get the container configuration for container name requested * * @param container * name of the Container (eg. blue) * @return a List of {@link org.opendaylight.controller.containermanager.ContainerConfig} * *
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/blue * * Response body in XML: * <container-config> * <container>blue</container> * <staticVlan>10</staticVlan> * <nodeConnectors>OF|1@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|23@OF|00:00:00:00:00:00:20:21</nodeConnectors> * </container-config> * * Response body in JSON: * { * "container-config": [ * { * "container": "yellow", * "staticVlan": "10", * "nodeConnectors": [ * "OF|1@OF|00:00:00:00:00:00:00:01", * "OF|2@OF|00:00:00:00:00:00:00:02" * ], * "flowSpecs": [] * } * ] * } **/ @Path("/container/{container}") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(ContainerConfig.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User is not authorized to perform this operation"), @ResponseCode(code = 403, condition = "Operation forbidden on default"), @ResponseCode(code = 404, condition = "The container is not found") }) public ContainerConfigs viewContainer(@PathParam(value = "container") String container) { handleContainerAuthorization(container, getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); List
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/yellow * * Request body in XML: * <container-config> * <container>yellow</container> * <staticVlan>10</staticVlan> * <nodeConnectors></nodeConnectors> * </container-config> * * Request body in JSON: * { * "container" : "yellow", * "nodeConnectors" : [ * "OF|1@OF|00:00:00:00:00:00:00:01", * "OF|23@OF|00:00:00:00:00:00:20:21" * ], * "staticVlan" : "10" * } * **/ @Path("/container/{container}") @PUT @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 201, condition = "Container created successfully"), @ResponseCode(code = 400, condition = "Invalid Container configuration."), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 403, condition = "Operation forbidden on default"), @ResponseCode(code = 404, condition = "Container Name is not found"), @ResponseCode(code = 409, condition = "Failed to create Container due to Conflicting Name"), @ResponseCode(code = 500, condition = "Failure Reason included in HTTP Error response") }) public Response createContainer(@Context UriInfo uriInfo, @PathParam(value = "container") String container, @TypeHint(ContainerConfig.class) ContainerConfig containerConfig) { handleAdminAuthorization(getUserName()); handleContainerExists(container); handleNameMismatch(containerConfig.getContainerName(), container); handleForbiddenOnDefault(container); IContainerManager containerManager = getContainerManager(); Status status = containerManager.addContainer(containerConfig); if (status.isSuccess()) { NorthboundUtils.auditlog("Container", username, "added", container); return Response.created(uriInfo.getRequestUri()).build(); } return NorthboundUtils.getResponse(status); } /** * Delete a container * * @param container * name of the Container (eg. green) * @return Response as dictated by the HTTP Response code * *
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/green * **/ @Path("/container/{container}") @DELETE @StatusCodes({ @ResponseCode(code = 204, condition = "Container deleted successfully"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 403, condition = "Operation forbidden on default"), @ResponseCode(code = 404, condition = "The container is not found") }) public Response removeContainer(@PathParam(value = "container") String container) { handleAdminAuthorization(getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); Status status = containerManager.removeContainer(container); if (status.isSuccess()) { NorthboundUtils.auditlog("Container", username, "removed", container); return Response.noContent().build(); } return NorthboundUtils.getResponse(status); } /** * Get flowspec within a given container * * @param container * name of the Container (eg. green) * @param name * name of the flowspec (eg. ssh) * @return flowspec detail as specified by: * {@link org.opendaylight.controller.containermanager.ContainerFlowConfig} * *
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/green/flowspec/ssh * * Response body in XML: * <flow-spec-config> * <name>ssh</name> * <nwSrc>10.0.0.101</nwSrc> * <nwDst>10.0.0.102</nwDst> * <protocol>IPv4</protocol> * <tpSrc>80</tpSrc> * <tpDst>100</tpDst> * </flow-spec-config> * * Response body in JSON: * { * "protocol" : "IPv4", * "nwDst" : "10.0.0.102", * "name" : "ssh", * "nwSrc" : "10.0.0.101", * "tpSrc" : "80", * "tpDst" : "100" * } * **/ @Path("/container/{container}/flowspec/{flowspec}") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(ContainerFlowConfig.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The container is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public ContainerFlowConfig viewContainerFlowSpec(@PathParam(value = "container") String container, @PathParam(value = "flowspec") String flowspec) { handleContainerAuthorization(container, getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); List
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/red/flowspec * * Response body in XML: * <flow-spec-configs> * <flow-spec-config> * <name>ssh</name> * <nwSrc>10.0.0.101</nwSrc> * <nwDst>10.0.0.102</nwDst> * <protocol>IPv4</protocol> * <tpSrc>23</tpSrc> * <tpDst>100</tpDst> * </flow-spec-config> * <flow-spec-config> * <name>http2</name> * <nwSrc>10.0.0.201</nwSrc> * <nwDst>10.0.0.202</nwDst> * <protocol></protocol> * <tpSrc>80</tpSrc> * <tpDst>100</tpDst> * </flow-spec-config> * </flow-spec-configs> * * Response body in JSON: * { * "flow-spec-config": [ * { * "name": "http", * "nwSrc": "10.0.0.201", * "nwDst": "10.0.0.202", * "protocol": "", * "tpSrc": "80", * "tpDst": "100" * }, * { * "name": "ssh", * "nwSrc": "10.0.0.101", * "nwDst": "10.0.0.102", * "protocol": "IPv4", * "tpSrc": "23", * "tpDst": "100" * } * ] * } * **/ @Path("/container/{container}/flowspecs") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(FlowSpecConfigs.class) @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 404, condition = "The container is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public FlowSpecConfigs viewContainerFlowSpecs(@PathParam(value = "container") String container) { handleContainerAuthorization(container, getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); return new FlowSpecConfigs(containerManager.getContainerFlows(container)); } /** * Add flowspec to a container * * @param container * name of the container (eg. purple) * @param name * name of the flowspec (eg. http) * @param flowspec * configuration as specified by: * {@link org.opendaylight.controller.containermanager.ContainerFlowConfig} * * @return Response as dictated by the HTTP Response code * *
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/purple/flowspec/http * * Request body in XML: * <flow-spec-config> * <name>http</name> * <nwSrc>10.0.0.101</nwSrc> * <nwDst>10.0.0.102</nwDst> * <protocol></protocol> * <tpSrc>80</tpSrc> * <tpDst>100</tpDst> * </flow-spec-config> * * Request body in JSON: * { * "protocol" : "", * "nwDst" : "10.0.0.102", * "name" : "http", * "nwSrc" : "10.0.0.101", * "tpSrc" : "80", * "tpDst" : "100" * } * **/ @Path("/container/{container}/flowspec/{flowspec}") @PUT @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 201, condition = "FlowSpec created successfully"), @ResponseCode(code = 400, condition = "Invalid flowspec configuration"), @ResponseCode(code = 404, condition = "The container is not found"), @ResponseCode(code = 409, condition = "Container Entry already exists"), @ResponseCode(code = 500, condition = "Failed to create Flow specifications. Failure Reason included in HTTP Error response") }) public Response createFlowSpec(@Context UriInfo uriInfo, @PathParam(value = "container") String container, @PathParam(value = "flowspec") String flowspec, @TypeHint(ContainerFlowConfig.class) ContainerFlowConfig containerFlowConfig) { handleAdminAuthorization(getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); handleNameMismatch(containerFlowConfig.getName(), flowspec); IContainerManager containerManager = getContainerManager(); List
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/black/flowspec/telnet * **/ @Path("/container/{container}/flowspec/{flowspec}") @DELETE @StatusCodes({ @ResponseCode(code = 204, condition = "Flow Spec deleted successfully"), @ResponseCode(code = 400, condition = "Invalid flowspec configuration"), @ResponseCode(code = 404, condition = "Container or Container Entry not found"), @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"), @ResponseCode(code = 500, condition = "Failed to delete Flowspec. Failure Reason included in HTTP Error response"), @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") }) public Response removeFlowSpec(@PathParam(value = "container") String container, @PathParam(value = "flowspec") String flowspec) { handleAdminAuthorization(getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); Set
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/green/nodeconnector * * Request body in XML: * <nodeConnectors> * <nodeConnectors>OF|1@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|2@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|3@OF|00:00:00:00:00:00:00:22</nodeConnectors> * <nodeConnectors>OF|4@OF|00:00:00:00:00:00:00:22</nodeConnectors> * </nodeConnectors> * * Request body in JSON: * { * "nodeConnectors" : [ * "OF|1@OF|00:00:00:00:00:00:00:01", * "OF|2@OF|00:00:00:00:00:00:00:01", * "OF|3@OF|00:00:00:00:00:00:00:22", * "OF|4@OF|00:00:00:00:00:00:00:22" * ] * } * **/ @Path("/container/{container}/nodeconnector/") @PUT @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(Response.class) @StatusCodes({ @ResponseCode(code = 200, condition = "NodeConnectors added successfully"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 403, condition = "Operation forbidden on default"), @ResponseCode(code = 404, condition = "The Container is not found"), @ResponseCode(code = 409, condition = "Container Entry already exists"), @ResponseCode(code = 500, condition = "Failed to create nodeconnectors. Failure Reason included in HTTP Error response") }) public Response addNodeConnectors(@PathParam(value = "container") String container, @TypeHint(StringList.class) StringList list) { handleAdminAuthorization(getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); Status status = containerManager.addContainerEntry(container, list.getList()); if (status.isSuccess()) { NorthboundUtils.auditlog("Node ", username, "added", " Ports:" + list.getList()); } return NorthboundUtils.getResponse(status); } /** * Remove node connectors from a container * * @param container * name of the container (eg. red) * @param list * The list of strings each representing a node connector in the form "
* * Example: * * Request URL: * http://localhost:8080/controller/nb/v2/containermanager/container/red/nodeconnector * * Request body in XML: * <nodeConnectors> * <nodeConnectors>OF|1@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|2@OF|00:00:00:00:00:00:00:01</nodeConnectors> * <nodeConnectors>OF|3@OF|00:00:00:00:00:00:00:22</nodeConnectors> * <nodeConnectors>OF|4@OF|00:00:00:00:00:00:00:22</nodeConnectors> * </nodeConnectors> * * Request body in JSON: * { * "nodeConnectors" : [ * "OF|1@OF|00:00:00:00:00:00:00:01", * "OF|2@OF|00:00:00:00:00:00:00:01", * "OF|3@OF|00:00:00:00:00:00:00:22", * "OF|4@OF|00:00:00:00:00:00:00:22" * ] * } * **/ @Path("/container/{container}/nodeconnector/") @DELETE @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 204, condition = "Container Entry deleted successfully"), @ResponseCode(code = 400, condition = "Invalid Container Entry configuration"), @ResponseCode(code = 404, condition = "The Container is not found"), @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"), @ResponseCode(code = 500, condition = "Failed to delete node connector. Failure Reason included in HTTP Error response") }) public Response removeNodeConnectors(@PathParam(value = "container") String container, @TypeHint(StringList.class) StringList portList) { handleAdminAuthorization(getUserName()); handleForbiddenOnDefault(container); handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); Status status = containerManager.removeContainerEntry(container, portList.getList()); if (status.isSuccess()) { NorthboundUtils.auditlog("Node", username, "removed", " Ports:" + portList.getList()); return Response.noContent().build(); } return NorthboundUtils.getResponse(status); } /* * Check If the function is not allowed on default container, Throw a * ResourceForbiddenException exception if forbidden */ private void handleForbiddenOnDefault(String container) { if (container.equalsIgnoreCase(GlobalConstants.DEFAULT.toString())) { throw new ResourceForbiddenException(RestMessages.NODEFAULT.toString() + ": " + container); } } /* * Check if container exists, Throw a ResourceNotFoundException exception if it * does not exist */ private void handleContainerNotExists(String container) { IContainerManager containerManager = getContainerManager(); if (!containerManager.doesContainerExist(container)) { throw new ResourceNotFoundException(RestMessages.NOCONTAINER.toString() + ": " + container); } } private void handleContainerExists(String container) { IContainerManager containerManager = getContainerManager(); if (containerManager.doesContainerExist(container)) { throw new ResourceConflictException(RestMessages.RESOURCECONFLICT.toString() + ": " + container); } } private void handleAdminAuthorization(String userName) { IUserManager usrMgr = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); UserLevel level = usrMgr.getUserLevel(userName); if (level.ordinal() <= UserLevel.NETWORKADMIN.ordinal()) { return; } throw new UnauthorizedException("User is not authorized to perform this operation"); } private void handleNetworkAuthorization(String userName) { IUserManager usrMgr = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); UserLevel level = usrMgr.getUserLevel(userName); if (level.ordinal() <= UserLevel.NETWORKOPERATOR.ordinal()) { return; } throw new UnauthorizedException("User is not authorized to perform this operation"); } private void handleContainerAuthorization(String container, String userName) { IContainerAuthorization auth = (IContainerAuthorization) ServiceHelper.getGlobalInstance( IContainerAuthorization.class, this); UserLevel level = auth.getUserLevel(userName); if (level.ordinal() <= UserLevel.NETWORKOPERATOR.ordinal()) { return; } Privilege current = (auth == null) ? Privilege.NONE : auth.getResourcePrivilege(userName, container); if (current.ordinal() > Privilege.NONE.ordinal()) { return; } throw new UnauthorizedException("User is not authorized to perform this operation"); } }