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
8 package org.opendaylight.controller.subnets.northbound;
10 import java.util.HashSet;
12 import java.util.List;
15 import javax.ws.rs.Consumes;
16 import javax.ws.rs.DELETE;
17 import javax.ws.rs.GET;
18 import javax.ws.rs.POST;
19 import javax.ws.rs.PUT;
20 import javax.ws.rs.Path;
21 import javax.ws.rs.PathParam;
22 import javax.ws.rs.Produces;
23 import javax.ws.rs.core.Context;
24 import javax.ws.rs.core.MediaType;
25 import javax.ws.rs.core.Response;
26 import javax.ws.rs.core.SecurityContext;
27 import javax.xml.bind.JAXBElement;
29 import org.codehaus.enunciate.jaxrs.ResponseCode;
30 import org.codehaus.enunciate.jaxrs.StatusCodes;
31 import org.codehaus.enunciate.jaxrs.TypeHint;
32 import org.opendaylight.controller.containermanager.IContainerManager;
33 import org.opendaylight.controller.northbound.commons.RestMessages;
34 import org.opendaylight.controller.northbound.commons.exception.BadRequestException;
35 import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
36 import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
37 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
38 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
39 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
40 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
41 import org.opendaylight.controller.sal.authorization.Privilege;
42 import org.opendaylight.controller.sal.utils.ServiceHelper;
43 import org.opendaylight.controller.sal.utils.Status;
44 import org.opendaylight.controller.switchmanager.ISwitchManager;
45 import org.opendaylight.controller.switchmanager.SubnetConfig;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * This class provides REST APIs to manage subnets.
54 * Authentication scheme : <b>HTTP Basic</b><br>
55 * Authentication realm : <b>opendaylight</b><br>
56 * Transport : <b>HTTP and HTTPS</b><br>
58 * HTTPS Authentication is disabled by default.
63 public class SubnetsNorthbound {
64 protected static final Logger logger = LoggerFactory.getLogger(SubnetsNorthbound.class);
66 private String username;
69 public void setSecurityContext(SecurityContext context) {
70 if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName();
73 protected String getUserName() {
77 private void handleContainerDoesNotExist(String containerName) {
78 IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
79 IContainerManager.class, this);
80 if (containerManager == null) {
81 throw new ServiceUnavailableException("Container " + RestMessages.NOCONTAINER.toString());
84 List<String> containerNames = containerManager.getContainerNames();
85 for (String cName : containerNames) {
86 if (cName.trim().equalsIgnoreCase(containerName.trim())) {
91 throw new ResourceNotFoundException(containerName + " " + RestMessages.NOCONTAINER.toString());
94 private void handleNameMismatch(String name, String nameinURL) {
95 if (name == null || nameinURL == null) {
96 throw new BadRequestException(RestMessages.INVALIDDATA.toString() + " : Name is null");
99 if (name.equals(nameinURL)) {
102 throw new ResourceConflictException(RestMessages.INVALIDDATA.toString()
103 + " : Name in URL does not match the name in request body");
107 * List all the subnets in a given container
109 * @param containerName
110 * container in which we want to query the subnets
112 * @return a List of SubnetConfig
117 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnets
120 * <subnetConfig>
121 * <name>subnet1</name>
122 * <subnet>30.0.0.1/24</subnet>
123 * <nodePorts>1/1>/nodePorts>
124 * <nodePorts>1/2>/nodePorts>
125 * <nodePorts>1/3>/nodePorts>
126 * </subnetConfig>
127 * <subnetConfig>
128 * <name>subnet2</name>
129 * <subnet>20.0.0.1/24</subnet>
130 * <nodePorts>2/1>/nodePorts>
131 * <nodePorts>2/2>/nodePorts>
132 * <nodePorts>2/3>/nodePorts>
133 * </subnetConfig>
138 * "subnet":"30.0.0.1/24",
139 * "nodePorts":["1/1","1/2","1/3"]
143 * "subnet":"20.0.0.1/24",
144 * "nodePorts":["2/1","2/2","2/3"]
148 @Path("/{containerName}/subnets")
150 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
151 @StatusCodes({ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
152 @ResponseCode(code = 404, condition = "The containerName passed was not found"),
153 @ResponseCode(code = 503, condition = "Service unavailable") })
154 @TypeHint(SubnetConfigs.class)
155 public SubnetConfigs listSubnets(@PathParam("containerName") String containerName) {
157 handleContainerDoesNotExist(containerName);
158 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
159 throw new UnauthorizedException("User is not authorized to perform this operation on container "
162 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
163 if (switchManager == null) {
164 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
166 return new SubnetConfigs(switchManager.getSubnetsConfigList());
170 * List the configuration of a subnet in a given container
172 * @param containerName
173 * container in which we want to query the subnet
175 * of the subnet being queried
177 * @return SubnetConfig
182 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1
185 * <subnetConfig>
186 * <name>subnet1</name>
187 * <subnet>30.0.0.1/24</subnet>
188 * <nodePorts>1/1>/nodePorts>
189 * <nodePorts>1/2>/nodePorts>
190 * <nodePorts>1/3>/nodePorts>
191 * </subnetConfig>
196 * "subnet":"30.0.0.1/24",
197 * "nodePorts":["1/1","1/2","1/3"]
201 @Path("/{containerName}/subnet/{subnetName}")
203 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
204 @StatusCodes({ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
205 @ResponseCode(code = 404, condition = "The containerName or subnetName passed was not found"),
206 @ResponseCode(code = 503, condition = "Service unavailable") })
207 @TypeHint(SubnetConfig.class)
208 public SubnetConfig listSubnet(@PathParam("containerName") String containerName,
209 @PathParam("subnetName") String subnetName) {
211 handleContainerDoesNotExist(containerName);
213 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
214 throw new UnauthorizedException("User is not authorized to perform this operation on container "
217 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
218 if (switchManager == null) {
219 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
221 SubnetConfig res = switchManager.getSubnetConfig(subnetName);
223 throw new ResourceNotFoundException(RestMessages.NOSUBNET.toString());
230 * Add a subnet to a container. If a subnet by the given name already exists
231 * this method will return a non-successful response.
233 * @param containerName
234 * name of the container to which subnet needs to be added
236 * name of new subnet to be added
237 * @param subnetConfigData
238 * the {@link SubnetConfig} structure in request body
240 * @return Response as dictated by the HTTP Response Status code
245 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1
248 * <subnetConfig>
249 * <name>subnet1</name>
250 * <subnet>30.0.0.1/24</subnet>
251 * </subnetConfig>
256 * "subnet":"30.0.0.1/24"
261 @Path("/{containerName}/subnet/{subnetName}")
263 @StatusCodes({ @ResponseCode(code = 201, condition = "Subnet created successfully"),
264 @ResponseCode(code = 400, condition = "Invalid data passed"),
265 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
266 @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
267 @ResponseCode(code = 404, condition = "Container name passed was not found or subnet config is null"),
268 @ResponseCode(code = 500, condition = "Internal Server Error: Addition of subnet failed"),
269 @ResponseCode(code = 503, condition = "Service unavailable") })
270 public Response addSubnet(@PathParam("containerName") String containerName,
271 @PathParam("subnetName") String subnetName,
272 @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
274 handleContainerDoesNotExist(containerName);
276 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
277 throw new UnauthorizedException("User is not authorized to perform this operation on container "
280 SubnetConfig cfgObject = subnetConfigData;
281 handleNameMismatch(cfgObject.getName(), subnetName);
283 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
284 if (switchManager == null) {
285 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
287 Set<String> ports = cfgObject.getNodePorts();
288 SubnetConfig subnetCfg = null;
290 subnetCfg = new SubnetConfig(cfgObject.getName(), cfgObject.getSubnet(), new HashSet<String>(0));
292 subnetCfg = cfgObject;
295 Status status = switchManager.addSubnet(subnetCfg);
297 if (status.isSuccess()) {
298 NorthboundUtils.auditlog("Subnet Gateway", username, "added", subnetName, containerName);
299 return Response.status(Response.Status.CREATED).build();
301 return NorthboundUtils.getResponse(status);
305 * Delete a subnet from a container
307 * @param containerName
308 * name of the container from which subnet needs to be removed
310 * name of new subnet to be deleted
311 * @return Response as dictated by the HTTP Response Status code
315 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1
319 @Path("/{containerName}/subnet/{subnetName}")
321 @StatusCodes({ @ResponseCode(code = 204, condition = "No Content"),
322 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
323 @ResponseCode(code = 404, condition = "The containerName passed was not found"),
324 @ResponseCode(code = 500, condition = "Internal Server Error : Removal of subnet failed"),
325 @ResponseCode(code = 503, condition = "Service unavailable") })
326 public Response removeSubnet(@PathParam("containerName") String containerName,
327 @PathParam("subnetName") String subnetName) {
329 handleContainerDoesNotExist(containerName);
331 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
332 throw new UnauthorizedException("User is not authorized to perform this operation on container "
336 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
337 if (switchManager == null) {
338 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
340 Status status = switchManager.removeSubnet(subnetName);
341 if (status.isSuccess()) {
342 NorthboundUtils.auditlog("Subnet Gateway", username, "removed", subnetName, containerName);
343 return Response.status(Response.Status.NO_CONTENT).build();
345 return NorthboundUtils.getResponse(status);
349 * Modify a subnet. For now only changing the port list is allowed.
351 * @param containerName
352 * Name of the Container
354 * Name of the SubnetConfig to be modified
355 * @param subnetConfigData
356 * the {@link SubnetConfig} structure in request body
358 * @return If the operation is successful or not
363 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1/nodePorts
366 * <subnetConfig>
367 * <name>subnet1</name>
368 * <subnet>30.0.0.1/24</subnet>
369 * <nodePorts>1/1</nodePorts>
370 * <nodePorts>1/2</nodePorts>
371 * <nodePorts>1/3</nodePorts>
372 * </subnetConfig>
377 * "subnet":"30.0.0.1/24",
378 * "nodePorts":["1/1","1/2","1/3"]
382 @Path("/{containerName}/subnet/{subnetName}/nodePorts")
384 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
386 @ResponseCode(code = 200, condition = "Ports replaced successfully"),
387 @ResponseCode(code = 400, condition = "Invalid request to change subnet name or invalid node ports passed"),
388 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
389 @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
390 @ResponseCode(code = 404, condition = "The containerName or subnetName is not found"),
391 @ResponseCode(code = 500, condition = "Internal server error: Modify ports failed"),
392 @ResponseCode(code = 503, condition = "Service unavailable") })
393 public Response modifySubnet(@PathParam("containerName") String containerName,
394 @PathParam("subnetName") String subnetName,
395 @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
397 handleContainerDoesNotExist(containerName);
399 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
400 throw new UnauthorizedException("User is not authorized to perform this operation on container "
403 handleNameMismatch(subnetConfigData.getName(), subnetName);
405 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
407 if (switchManager == null) {
408 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
411 SubnetConfig subnetConf = subnetConfigData;
412 SubnetConfig existingConf = switchManager.getSubnetConfig(subnetName);
414 boolean successful = true;
416 // make sure that the name matches an existing subnet and we're not
417 // changing the name or subnet IP/mask
418 if (existingConf == null) {
419 // don't have a subnet by that name
420 return Response.status(Response.Status.NOT_FOUND).build();
422 } else if (!existingConf.getName().equals(subnetConf.getName())
423 || !existingConf.getSubnet().equals(subnetConf.getSubnet())) {
424 // can't change the name of a subnet
425 Response.status(Response.Status.BAD_REQUEST).build();
427 // create a set for fast lookups
428 Set<String> newPorts = new HashSet<String>(subnetConf.getNodePorts());
430 // go through the current ports and (1) remove ports that aren't
431 // there anymore and (2) remove ports that are still there from the
432 // set of ports to add
433 for (String s : existingConf.getNodePorts()) {
434 if (newPorts.contains(s)) {
437 Status st = switchManager.removePortsFromSubnet(subnetName, s);
438 successful = successful && st.isSuccess();
442 // add any remaining ports
443 for (String s : newPorts) {
444 Status st = switchManager.addPortsToSubnet(subnetName, s);
445 successful = successful && st.isSuccess();
447 NorthboundUtils.auditlog("Subnet Gateway", username, "added", s + " to " + subnetName,
454 return Response.status(Response.Status.OK).build();
456 throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());
460 * Add ports to a subnet in the container.
462 * @param containerName
463 * name of the container that has the subnet to which node ports
466 * name of subnet to which node ports need to be added
467 * @param SubnetConfig
468 * the {@link SubnetConfig} structure in request body
469 * @return Response as dictated by the HTTP Response Status code
473 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1/nodePorts
476 * <subnetConfig>
477 * <name>subnet1</name>
478 * <subnet>30.0.0.1/24</subnet>
479 * <nodePorts>1/1</nodePorts>
480 * <nodePorts>1/2</nodePorts>
481 * <nodePorts>1/3</nodePorts>
482 * </subnetConfig>
487 * "subnet":"30.0.0.1/24",
488 * "nodePorts":["1/1","1/2","1/3"]
493 @Path("/{containerName}/subnet/{subnetName}/nodePorts")
495 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
497 @ResponseCode(code = 200, condition = "Added node ports to subnet successfully"),
498 @ResponseCode(code = 400, condition = "Invalid request to change subnet name or invalid node ports passed"),
499 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
500 @ResponseCode(code = 404, condition = "The containerName or subnet is not found"),
501 @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
502 @ResponseCode(code = 500, condition = "Internal server error : Port add failed"),
503 @ResponseCode(code = 503, condition = "Service unavailable") })
504 public Response addNodePorts(@PathParam("containerName") String containerName,
505 @PathParam("subnetName") String subnetName,
506 @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
508 handleContainerDoesNotExist(containerName);
510 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
511 throw new UnauthorizedException("User is not authorized to perform this operation on container "
514 handleNameMismatch(subnetConfigData.getName(), subnetName);
516 SubnetConfig subnetConf = subnetConfigData;
518 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
520 if (switchManager == null) {
521 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
524 SubnetConfig existingConf = switchManager.getSubnetConfig(subnetName);
526 // make sure that the name matches an existing subnet and we're not
527 // changing the name or subnet IP/mask
528 if (existingConf == null) {
529 // don't have a subnet by that name
530 return Response.status(Response.Status.NOT_FOUND).build();
531 } else if (!existingConf.getName().equals(subnetConf.getName())
532 || !existingConf.getSubnet().equals(subnetConf.getSubnet())) {
533 // can't change the name of a subnet
534 return Response.status(Response.Status.BAD_REQUEST).build();
537 boolean successful = true;
538 Set<String> ports = subnetConf.getNodePorts();
540 if (ports == null || ports.isEmpty()) {
541 throw new BadRequestException(RestMessages.INVALIDDATA.toString());
544 // add new ports only
545 if (existingConf.getNodePorts() != null) {
546 ports.removeAll(existingConf.getNodePorts());
548 for (String port : ports) {
549 st = switchManager.addPortsToSubnet(subnetName, port);
550 successful = successful && st.isSuccess();
552 NorthboundUtils.auditlog("Subnet Gateway", username, "added", st + " to " + subnetName, containerName);
556 return Response.status(Response.Status.OK).build();
558 throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());
562 * Delete ports from a subnet in the container
564 * @param containerName
565 * name of the container that has the subnet from which node
566 * ports need to be deleted
568 * name of subnet from which node ports need to be deleted
569 * @param subnetConfigData
570 * SubnetConfig object to be deleted
571 * @return Response as dictated by the HTTP Response Status code
575 * Request URL: http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/subnet1/nodePorts
578 * <subnetConfig>
579 * <name>subnet3</name>
580 * <subnet>30.0.0.1/24</subnet>
581 * <nodePorts>1/1,1/2,1/3</nodePorts>
582 * </subnetConfig>
585 * { "name" : "subnet1",
586 * "subnet" : "30.0.0.1/24",
587 * nodePorts : ["1/1,1/2,1/3"]}
591 @Path("/{containerName}/subnet/{subnetName}/nodePorts")
593 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
595 @ResponseCode(code = 204, condition = "No content"),
596 @ResponseCode(code = 400, condition = "Invalid request to change subnet name or invalid node ports passed"),
597 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
598 @ResponseCode(code = 404, condition = "The containerName or subnet is not found"),
599 @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
600 @ResponseCode(code = 500, condition = "Internal server error : Delete node ports failed"),
601 @ResponseCode(code = 503, condition = "Service unavailable") })
602 public Response deleteNodePorts(@PathParam("containerName") String containerName,
603 @PathParam("subnetName") String subnetName,
604 @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
606 handleContainerDoesNotExist(containerName);
608 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
609 throw new UnauthorizedException("User is not authorized to perform this operation on container "
612 handleNameMismatch(subnetConfigData.getName(), subnetName);
614 SubnetConfig subnetConf = subnetConfigData;
616 if (subnetConf.getNodePorts() == null || subnetConf.getNodePorts().isEmpty()) {
617 throw new BadRequestException(RestMessages.INVALIDDATA.toString() + " : invalid node ports");
620 ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
622 if (switchManager == null) {
623 throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
626 SubnetConfig existingConf = switchManager.getSubnetConfig(subnetName);
628 // make sure that the name matches an existing subnet and we're not
629 // changing the name or subnet IP/mask
630 if (existingConf == null) {
631 // don't have a subnet by that name
632 return Response.status(Response.Status.NOT_FOUND).build();
633 } else if (!existingConf.getName().equals(subnetConf.getName())
634 || !existingConf.getSubnet().equals(subnetConf.getSubnet())) {
635 // can't change the name of a subnet
636 return Response.status(Response.Status.BAD_REQUEST).build();
639 boolean successful = true;
640 Set<String> ports = subnetConf.getNodePorts();
642 // delete existing ports
643 ports.retainAll(existingConf.getNodePorts());
644 for (String port : ports) {
645 st = switchManager.removePortsFromSubnet(subnetName, port);
646 successful = successful && st.isSuccess();
648 NorthboundUtils.auditlog("Subnet Gateway", username, "removed", st + " from " + subnetName,
653 return Response.noContent().build();
655 throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());