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