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