3465dc95addd727ade9243e3410208cc7735ec40
[controller.git] / opendaylight / northbound / subnets / src / main / java / org / opendaylight / controller / subnets / northbound / SubnetsNorthbound.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.subnets.northbound;
9
10 import java.util.HashSet;
11 import java.util.List;
12 import java.util.Set;
13
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.ws.rs.core.UriInfo;
27
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.northbound.commons.RestMessages;
33 import org.opendaylight.controller.northbound.commons.exception.BadRequestException;
34 import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
35 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
36 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
37 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
38 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
39 import org.opendaylight.controller.sal.authorization.Privilege;
40 import org.opendaylight.controller.sal.core.NodeConnector;
41 import org.opendaylight.controller.sal.utils.ServiceHelper;
42 import org.opendaylight.controller.sal.utils.Status;
43 import org.opendaylight.controller.switchmanager.ISwitchManager;
44 import org.opendaylight.controller.switchmanager.SubnetConfig;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * This class provides REST APIs to manage subnets.
50  *
51  * <br>
52  * <br>
53  * Authentication scheme : <b>HTTP Basic</b><br>
54  * Authentication realm : <b>opendaylight</b><br>
55  * Transport : <b>HTTP and HTTPS</b><br>
56  * <br>
57  * HTTPS Authentication is disabled by default.
58  *
59  */
60
61 @Path("/")
62 public class SubnetsNorthbound {
63     protected static final Logger logger = LoggerFactory.getLogger(SubnetsNorthbound.class);
64
65     private String username;
66
67     @Context
68     public void setSecurityContext(SecurityContext context) {
69         if (context != null && context.getUserPrincipal() != null) {
70             username = context.getUserPrincipal().getName();
71         }
72     }
73
74     protected String getUserName() {
75         return username;
76     }
77
78     private void handleContainerDoesNotExist(String containerName) {
79         IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
80                 IContainerManager.class, this);
81         if (containerManager == null) {
82             throw new ServiceUnavailableException("Container " + RestMessages.NOCONTAINER.toString());
83         }
84
85         List<String> containerNames = containerManager.getContainerNames();
86         for (String cName : containerNames) {
87             if (cName.trim().equalsIgnoreCase(containerName.trim())) {
88                 return;
89             }
90         }
91
92         throw new ResourceNotFoundException(containerName + " " + RestMessages.NOCONTAINER.toString());
93     }
94
95     private void handleNameMismatch(String name, String nameinURL) {
96         if (name == null || nameinURL == null) {
97             throw new BadRequestException(RestMessages.INVALIDDATA.toString() + " : Name is null");
98         }
99
100         if (name.equals(nameinURL)) {
101             return;
102         }
103         throw new ResourceConflictException(RestMessages.INVALIDDATA.toString()
104                 + " : Name in URL does not match the name in request body");
105     }
106
107     /**
108      * List all the subnets in a given container
109      *
110      * @param containerName
111      *            container in which we want to query the subnets
112      *
113      * @return a List of SubnetConfig
114      *
115      * <pre>
116      * Example:
117      *
118      * Request URL:
119      * http://localhost:8080/controller/nb/v2/subnetservice/default/subnets
120      *
121      * Response body in XML:
122      * &lt;list&gt;
123      * &lt;subnetConfig&gt;
124      *    &lt;name&gt;marketingdepartment&lt;/name&gt;
125      *    &lt;subnet&gt;30.31.54.254/24&lt;/subnet&gt;
126      * &lt;/subnetConfig&gt;
127      * &lt;subnetConfig&gt;
128      *    &lt;name&gt;salesdepartment&lt;/name&gt;
129      *    &lt;subnet&gt;20.18.1.254/16&lt;/subnet&gt;
130      *    &lt;nodeConnectors&gt;OF|11@OF|00:00:00:aa:bb:cc:dd:ee&lt;/nodeConnectors&gt;
131      *    &lt;nodeConnectors&gt;OF|13@OF|00:00:00:aa:bb:cc:dd:ee&lt;/nodeConnectors&gt;
132      * &lt;/subnetConfig&gt;
133      * &lt;/list&gt;
134      * Response body in JSON:
135      * {
136      *   "subnetConfig": [
137      *     {
138      *       "name": "marketingdepartment",
139      *       "subnet": "30.31.54.254/24",
140      *       "nodeConnectors": [
141      *           "OF|04@OF|00:00:00:00:00:00:00:04",
142      *           "OF|07@OF|00:00:00:00:00:00:00:07"
143      *       ]
144      *     },
145      *     {
146      *       "name":"salesdepartment",
147      *       "subnet":"20.18.1.254/16",
148      *       "nodeConnectors": [
149      *            "OF|11@OF|00:00:00:aa:bb:cc:dd:ee",
150      *            "OF|13@OF|00:00:00:aa:bb:cc:dd:ee"
151      *        ]
152      *      }
153      *   ]
154      * }
155      *
156      * </pre>
157      */
158     @Path("/{containerName}/subnets")
159     @GET
160     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
161     @StatusCodes({ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
162         @ResponseCode(code = 404, condition = "The containerName passed was not found"),
163         @ResponseCode(code = 503, condition = "Service unavailable") })
164     @TypeHint(SubnetConfigs.class)
165     public SubnetConfigs listSubnets(@PathParam("containerName") String containerName) {
166
167         handleContainerDoesNotExist(containerName);
168         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
169             throw new UnauthorizedException("User is not authorized to perform this operation on container "
170                     + containerName);
171         }
172         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
173                 this);
174         if (switchManager == null) {
175             throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
176         }
177         return new SubnetConfigs(switchManager.getSubnetsConfigList());
178     }
179
180     /**
181      * List the configuration of a subnet in a given container
182      *
183      * @param containerName
184      *            container in which we want to query the subnet
185      * @param subnetName
186      *            of the subnet being queried
187      *
188      * @return SubnetConfig
189      *
190      *         <pre>
191      * Example:
192      *
193      * Request URL:
194      * http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/marketingdepartment
195      *
196      * Response body in XML:
197      * &lt;subnetConfig&gt;
198      *    &lt;name&gt;marketingdepartment&lt;/name&gt;
199      *    &lt;subnet&gt;30.0.0.1/24&lt;/subnet&gt;
200      *    &lt;nodeConnectors&gt;OF|1@OF|00:00:00:00:00:00:00:01&lt;/nodePorts&gt;
201      *    &lt;nodeConnectors&gt;OF|3@OF|00:00:00:00:00:00:00:03&lt;/nodePorts&gt;
202      * &lt;/subnetConfig&gt;
203      *
204      * Response body in JSON:
205      * {
206      *  "name":"marketingdepartment",
207      *  "subnet":"30.0.0.1/24",
208      *  "nodeConnectors":[
209      *       "OF|1@OF|00:00:00:00:00:00:00:01",
210      *       "OF|3@OF|00:00:00:00:00:00:00:03"
211      *   ]
212      * }
213      * </pre>
214      */
215     @Path("/{containerName}/subnet/{subnetName}")
216     @GET
217     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
218     @StatusCodes({ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
219         @ResponseCode(code = 404, condition = "The containerName or subnetName passed was not found"),
220         @ResponseCode(code = 503, condition = "Service unavailable") })
221     @TypeHint(SubnetConfig.class)
222     public SubnetConfig listSubnet(@PathParam("containerName") String containerName,
223             @PathParam("subnetName") String subnetName) {
224
225         handleContainerDoesNotExist(containerName);
226
227         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
228             throw new UnauthorizedException("User is not authorized to perform this operation on container "
229                     + containerName);
230         }
231         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
232                 this);
233         if (switchManager == null) {
234             throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
235         }
236         SubnetConfig res = switchManager.getSubnetConfig(subnetName);
237         if (res == null) {
238             throw new ResourceNotFoundException(RestMessages.NOSUBNET.toString());
239         }
240         return res;
241     }
242
243     /**
244      * Add a subnet into the specified container context, node connectors are
245      * optional
246      *
247      * @param containerName
248      *            name of the container context in which the subnet needs to be
249      *            added
250      * @param subnetName
251      *            name of new subnet to be added
252      * @param subnetConfigData
253      *            the {@link SubnetConfig} structure in request body
254      *
255      * @return Response as dictated by the HTTP Response Status code
256      *
257      *         <pre>
258      * Example:
259      *
260      * Request URL:
261      * http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/salesdepartment
262      *
263      * Request body in XML:
264      *  &lt;subnetConfig&gt;
265      *      &lt;name&gt;salesdepartment&lt;/name&gt;
266      *      &lt;subnet&gt;172.173.174.254/24&lt;/subnet&gt;
267      *      &lt;nodeConnectors&gt;OF|22@OF|00:00:11:22:33:44:55:66&lt;/nodeConnectors&gt;
268      *      &lt;nodeConnectors&gt;OF|39@OF|00:00:ab:cd:33:44:55:66&lt;/nodeConnectors&gt;
269      *  &lt;/subnetConfig&gt;
270      *
271      * Request body in JSON:
272      * {
273      *  "name":"salesdepartment",
274      *  "subnet":"172.173.174.254/24",
275      *  "nodeConnectors":[
276      *       "OF|22@OF|00:00:11:22:33:44:55:66",
277      *       "OF|39@OF|00:00:ab:cd:33:44:55:66"
278      *       ]
279      * }
280      * </pre>
281      */
282
283     @Path("/{containerName}/subnet/{subnetName}")
284     @PUT
285     @StatusCodes({ @ResponseCode(code = 201, condition = "Subnet created successfully"),
286         @ResponseCode(code = 400, condition = "Invalid data passed"),
287         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
288         @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
289         @ResponseCode(code = 404, condition = "Container name passed was not found or subnet config is null"),
290         @ResponseCode(code = 500, condition = "Internal Server Error: Addition of subnet failed"),
291         @ResponseCode(code = 503, condition = "Service unavailable") })
292     public Response addSubnet(@PathParam("containerName") String containerName,
293             @PathParam("subnetName") String subnetName, @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
294
295         handleContainerDoesNotExist(containerName);
296
297         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
298             throw new UnauthorizedException("User is not authorized to perform this operation on container "
299                     + containerName);
300         }
301         SubnetConfig cfgObject = subnetConfigData;
302         handleNameMismatch(cfgObject.getName(), subnetName);
303
304         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
305                 this);
306         if (switchManager == null) {
307             throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
308         }
309         Status status = switchManager.addSubnet(cfgObject);
310         if (status.isSuccess()) {
311             NorthboundUtils.auditlog("Subnet Gateway", username, "added", subnetName, containerName);
312             if (subnetConfigData.getNodeConnectors() != null) {
313                 for (NodeConnector port : subnetConfigData.getNodeConnectors()) {
314                     NorthboundUtils.auditlog("Port", getUserName(), "added",
315                             NorthboundUtils.getPortName(port, switchManager) + " to Subnet Gateway " + subnetName,
316                             containerName);
317                 }
318             }
319             return Response.status(Response.Status.CREATED).build();
320         }
321         return NorthboundUtils.getResponse(status);
322     }
323
324     /**
325      * Delete a subnet from the specified container context
326      *
327      * @param containerName
328      *            name of the container in which subnet needs to be removed
329      * @param subnetName
330      *            name of new subnet to be deleted
331      * @return Response as dictated by the HTTP Response Status code
332      *
333      * <pre>
334      * Example:
335      *
336      * Request URL:
337      * http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/engdepartment
338      *
339      * </pre>
340      */
341     @Path("/{containerName}/subnet/{subnetName}")
342     @DELETE
343     @StatusCodes({ @ResponseCode(code = 204, condition = "No Content"),
344         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
345         @ResponseCode(code = 404, condition = "The containerName passed was not found"),
346         @ResponseCode(code = 500, condition = "Internal Server Error : Removal of subnet failed"),
347         @ResponseCode(code = 503, condition = "Service unavailable") })
348     public Response removeSubnet(@PathParam("containerName") String containerName,
349             @PathParam("subnetName") String subnetName) {
350
351         handleContainerDoesNotExist(containerName);
352
353         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
354             throw new UnauthorizedException("User is not authorized to perform this operation on container "
355                     + containerName);
356         }
357
358         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
359                 this);
360         if (switchManager == null) {
361             throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
362         }
363         Status status = switchManager.removeSubnet(subnetName);
364         if (status.isSuccess()) {
365             NorthboundUtils.auditlog("Subnet Gateway", username, "removed", subnetName, containerName);
366             return Response.status(Response.Status.NO_CONTENT).build();
367         }
368         return NorthboundUtils.getResponse(status);
369     }
370
371     /**
372      * Modify a subnet. Replace the existing subnet with the new specified one.
373      * For now only port list modification is allowed. If the respective subnet
374      * configuration does not exist this call is equivalent to a subnet
375      * creation.
376      *
377      * @param containerName
378      *            Name of the Container context
379      * @param subnetName
380      *            Name of the subnet to be modified
381      * @param subnetConfigData
382      *            the {@link SubnetConfig} structure in request body parameter
383      * @return Response as dictated by the HTTP Response Status code
384      *
385      *         <pre>
386      * Example:
387      *
388      * Request URL:
389      * http://localhost:8080/controller/nb/v2/subnetservice/default/subnet/salesdepartment
390      *
391      *  Request body in XML:
392      *  &lt;subnetConfig&gt;
393      *      &lt;name&gt;salesdepartment&lt;/name&gt;
394      *      &lt;subnet&gt;172.173.174.254/24&lt;/subnet&gt;
395      *      &lt;nodeConnectors&gt;OF|22@OF|00:00:11:22:33:44:55:66&lt;/nodeConnectors&gt;
396      *      &lt;nodeConnectors&gt;OF|39@OF|00:00:ab:cd:33:44:55:66&lt;/nodeConnectors&gt;
397      *  &lt;/subnetConfig&gt;
398      *
399      * Request body in JSON:
400      * {
401      *  "name":"salesdepartment",
402      *  "subnet":"172.173.174.254/24",
403      *  "nodeConnectors":[
404      *      "OF|22@OF|00:00:11:22:33:44:55:66",
405      *      "OF|39@OF|00:00:ab:cd:33:44:55:66"
406      *  ]
407      * }
408      * </pre>
409      */
410     @Path("/{containerName}/subnet/{subnetName}")
411     @POST
412     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
413     @StatusCodes({ @ResponseCode(code = 200, condition = "Configuration replaced successfully"),
414         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
415         @ResponseCode(code = 409, condition = "Subnet name in url conflicts with name in request body"),
416         @ResponseCode(code = 404, condition = "The containerName or subnetName is not found"),
417         @ResponseCode(code = 500, condition = "Internal server error: Modify subnet failed"),
418         @ResponseCode(code = 503, condition = "Service unavailable") })
419     public Response modifySubnet(@Context UriInfo uriInfo, @PathParam("containerName") String containerName,
420             @PathParam("subnetName") String subnetName, @TypeHint(SubnetConfig.class) SubnetConfig subnetConfigData) {
421
422         handleContainerDoesNotExist(containerName);
423
424         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
425             throw new UnauthorizedException("User is not authorized to perform this operation on container "
426                     + containerName);
427         }
428         handleNameMismatch(subnetConfigData.getName(), subnetName);
429
430         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
431                 this);
432         if (switchManager == null) {
433             throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString());
434         }
435
436         // Need to check this until Status does not return a CREATED status code
437         SubnetConfig existingConf = switchManager.getSubnetConfig(subnetName);
438
439         Status status = switchManager.modifySubnet(subnetConfigData);
440
441         if (status.isSuccess()) {
442             if (existingConf == null) {
443                 NorthboundUtils.auditlog("Subnet Gateway", username, "added", subnetName, containerName);
444                 if (subnetConfigData.getNodeConnectors() != null) {
445                     for (NodeConnector port : subnetConfigData.getNodeConnectors()) {
446                         NorthboundUtils.auditlog("Port", getUserName(), "added",
447                                 NorthboundUtils.getPortName(port, switchManager) + " to Subnet Gateway" + subnetName,
448                                 containerName);
449                     }
450                 }
451                 return Response.created(uriInfo.getRequestUri()).build();
452             } else {
453                 Set<NodeConnector> existingNCList = existingConf.getNodeConnectors();
454
455                 if (existingNCList == null) {
456                     existingNCList = new HashSet<NodeConnector>(0);
457                 }
458                 if (subnetConfigData.getNodeConnectors() != null) {
459                     for (NodeConnector port : subnetConfigData.getNodeConnectors()) {
460                         if (!existingNCList.contains(port)) {
461                             NorthboundUtils.auditlog("Port", getUserName(), "added",
462                                     NorthboundUtils.getPortName(port, switchManager) + " to Subnet Gateway "
463                                             + subnetName, containerName);
464                         }
465                     }
466                 }
467                 for (NodeConnector port : existingNCList) {
468                     if (!subnetConfigData.getNodeConnectors().contains(port)) {
469                         NorthboundUtils
470                                 .auditlog("Port", getUserName(), "removed",
471                                         NorthboundUtils.getPortName(port, switchManager) + " from Subnet Gateway "
472                                                 + subnetName, containerName);
473                     }
474                 }
475             }
476         }
477         return NorthboundUtils.getResponse(status);
478     }
479 }