Support for FIQL queries on REST retreive operations
[controller.git] / opendaylight / northbound / switchmanager / src / main / java / org / opendaylight / controller / switchmanager / northbound / SwitchNorthbound.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
9 package org.opendaylight.controller.switchmanager.northbound;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17
18 import javax.ws.rs.Consumes;
19 import javax.ws.rs.DELETE;
20 import javax.ws.rs.GET;
21 import javax.ws.rs.POST;
22 import javax.ws.rs.PUT;
23 import javax.ws.rs.Path;
24 import javax.ws.rs.PathParam;
25 import javax.ws.rs.Produces;
26 import javax.ws.rs.QueryParam;
27 import javax.ws.rs.core.Context;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.SecurityContext;
31 import javax.ws.rs.core.UriInfo;
32 import javax.ws.rs.ext.ContextResolver;
33
34 import org.codehaus.enunciate.jaxrs.ResponseCode;
35 import org.codehaus.enunciate.jaxrs.StatusCodes;
36 import org.codehaus.enunciate.jaxrs.TypeHint;
37 import org.opendaylight.controller.containermanager.IContainerManager;
38 import org.opendaylight.controller.northbound.commons.RestMessages;
39 import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
40 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
41 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
42 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
43 import org.opendaylight.controller.northbound.commons.query.QueryContext;
44 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
45 import org.opendaylight.controller.sal.authorization.Privilege;
46 import org.opendaylight.controller.sal.core.Node;
47 import org.opendaylight.controller.sal.core.NodeConnector;
48 import org.opendaylight.controller.sal.core.Property;
49 import org.opendaylight.controller.sal.utils.GlobalConstants;
50 import org.opendaylight.controller.sal.utils.ServiceHelper;
51 import org.opendaylight.controller.sal.utils.Status;
52 import org.opendaylight.controller.sal.utils.StatusCode;
53 import org.opendaylight.controller.switchmanager.ISwitchManager;
54 import org.opendaylight.controller.switchmanager.SwitchConfig;
55
56 /**
57  * The class provides Northbound REST APIs to access the nodes, node connectors
58  * and their properties.
59  *
60  */
61
62 @Path("/")
63 public class SwitchNorthbound {
64
65     private String username;
66     private QueryContext queryContext;
67
68     @Context
69     public void setQueryContext(ContextResolver<QueryContext> queryCtxResolver) {
70       if (queryCtxResolver != null) {
71         queryContext = queryCtxResolver.getContext(QueryContext.class);
72       }
73     }
74
75     @Context
76     public void setSecurityContext(SecurityContext context) {
77         if (context != null && context.getUserPrincipal() != null) {
78             username = context.getUserPrincipal().getName();
79         }
80     }
81
82     protected String getUserName() {
83         return username;
84     }
85
86     private ISwitchManager getIfSwitchManagerService(String containerName) {
87         IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
88                 IContainerManager.class, this);
89         if (containerManager == null) {
90             throw new ServiceUnavailableException("Container " + RestMessages.SERVICEUNAVAILABLE.toString());
91         }
92
93         boolean found = false;
94         List<String> containerNames = containerManager.getContainerNames();
95         for (String cName : containerNames) {
96             if (cName.trim().equalsIgnoreCase(containerName.trim())) {
97                 found = true;
98                 break;
99             }
100         }
101
102         if (found == false) {
103             throw new ResourceNotFoundException(containerName + " " + RestMessages.NOCONTAINER.toString());
104         }
105
106         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName,
107                 this);
108
109         if (switchManager == null) {
110             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
111         }
112
113         return switchManager;
114     }
115
116     /**
117      *
118      * Retrieve a list of all the nodes and their properties in the network
119      *
120      * @param containerName
121      *            Name of the Container (Eg. 'default')
122      * @return A list of Pair each pair represents a
123      *         {@link org.opendaylight.controller.sal.core.Node} and Set of
124      *         {@link org.opendaylight.controller.sal.core.Property} attached to
125      *         it.
126      *
127      *         <pre>
128      *
129      * Example:
130      *
131      * Request URL:
132      * http://localhost:8080/controller/nb/v2/switchmanager/default/nodes
133      *
134      * Response body in XML:
135      * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
136      * &lt;list&gt;
137      *     &#x20;&#x20;&#x20;&lt;nodeProperties&gt;
138      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;node&gt;
139      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;id&gt;00:00:00:00:00:00:00:02&lt;/id&gt;
140      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;type&gt;OF&lt;/type&gt;
141      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/node&gt;
142      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;properties&gt;
143      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;tables&gt;
144      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;-1&lt;/value&gt;
145      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/tables&gt;
146      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;description&gt;
147      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;Switch2&lt;/value&gt;
148      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/description&gt;
149      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;actions&gt;
150      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;4095&lt;/value&gt;
151      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/actions&gt;
152      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;macAddress&gt;
153      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;00:00:00:00:00:02&lt;/value&gt;
154      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/macAddress&gt;
155      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;capabilities&gt;
156      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;199&lt;/value&gt;
157      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/capabilities&gt;
158      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;timeStamp&gt;
159      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;1377291227877&lt;/value&gt;
160      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;name&gt;connectedSince&lt;/name&gt;
161      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/timeStamp&gt;
162      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;buffers&gt;
163      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;256&lt;/value&gt;
164      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/buffers&gt;
165      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/properties&gt;
166      *     &#x20;&#x20;&#x20;&lt;/nodeProperties&gt;
167      * &lt;/list&gt;
168      *
169      * Response body in JSON:
170      * {
171      *    "nodeProperties":[
172      *       {
173      *          "node":{
174      *             "id":"00:00:00:00:00:00:00:02",
175      *             "type":"OF"
176      *          },
177      *          "properties":{
178      *             "tables":{
179      *                "value":"-1"
180      *             },
181      *             "description":{
182      *                "value":"None"
183      *             },
184      *             "actions":{
185      *                "value":"4095"
186      *             },
187      *             "macAddress":{
188      *                "value":"00:00:00:00:00:02"
189      *             },
190      *             "capabilities":{
191      *                "value":"199"
192      *             },
193      *             "timeStamp":{
194      *                "value":"1377291227877",
195      *                "name":"connectedSince"
196      *             },
197      *             "buffers":{
198      *                "value":"256"
199      *             }
200      *          }
201      *       }
202      *    ]
203      * }
204      *
205      * </pre>
206      */
207     @Path("/{containerName}/nodes")
208     @GET
209     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
210     @TypeHint(Nodes.class)
211     @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
212         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
213         @ResponseCode(code = 404, condition = "The containerName is not found"),
214         @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable"),
215         @ResponseCode(code = 400, condition = "Incorrect query syntex") })
216     public Nodes getNodes(@PathParam("containerName") String containerName, @QueryParam("_q") String queryString) {
217
218         if (!isValidContainer(containerName)) {
219             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
220         }
221
222         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
223             throw new UnauthorizedException("User is not authorized to perform this operation on container "
224                     + containerName);
225         }
226
227         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
228         if (switchManager == null) {
229             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
230         }
231
232         List<NodeProperties> res = new ArrayList<NodeProperties>();
233         Set<Node> nodes = switchManager.getNodes();
234         if (nodes == null) {
235             return new Nodes(res);
236         }
237
238         for (Node node : nodes) {
239             Map<String, Property> propMap = switchManager.getNodeProps(node);
240             if (propMap == null) {
241                 continue;
242             }
243             Set<Property> props = new HashSet<Property>(propMap.values());
244
245             NodeProperties nodeProps = new NodeProperties(node, props);
246             res.add(nodeProps);
247         }
248         Nodes result = new Nodes(res);
249         if (queryString != null) {
250             queryContext.createQuery(queryString, Nodes.class)
251                 .filter(result, NodeProperties.class);
252         }
253         return result;
254     }
255
256     /**
257      * Add a Description, Tier and Forwarding mode property to a node. This
258      * method returns a non-successful response if a node by that name already
259      * exists.
260      *
261      * @param containerName
262      *            Name of the Container (Eg. 'default')
263      * @param nodeType
264      *            Type of the node being programmed (Eg. 'OF')
265      * @param nodeId
266      *            Node Identifier as specified by
267      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
268      *            '00:00:00:00:00:00:00:03')
269      * @param propertyName
270      *            Name of the Property. Properties that can be configured are:
271      *            description, forwarding(only for default container) and tier
272      * @param propertyValue
273      *            Value of the Property. Description can be any string (Eg.
274      *            'Node1'), valid values for tier are non negative numbers, and
275      *            valid values for forwarding are 0 for reactive and 1 for
276      *            proactive forwarding.
277      * @return Response as dictated by the HTTP Response Status code
278      *
279      *         <pre>
280      *
281      * Example:
282      *
283      * Request URL:
284      * http://localhost:8080/controller/nb/v2/switchmanager/default/node/OF/00:00:00:00:00:00:00:03/property/description/Switch3
285      *
286      * </pre>
287      */
288
289     @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}/{propertyValue}")
290     @PUT
291     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
292     @TypeHint(Response.class)
293     @StatusCodes({
294         @ResponseCode(code = 201, condition = "Operation successful"),
295         @ResponseCode(code = 400, condition = "The nodeId or configuration is invalid"),
296         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
297         @ResponseCode(code = 404, condition = "The Container Name or node or configuration name is not found"),
298         @ResponseCode(code = 406, condition = "The property cannot be configured in non-default container"),
299         @ResponseCode(code = 409, condition = "Unable to update configuration due to cluster conflict or conflicting description property"),
300         @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
301     public Response addNodeProperty(@Context UriInfo uriInfo, @PathParam("containerName") String containerName,
302             @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId,
303             @PathParam("propertyName") String propertyName, @PathParam("propertyValue") String propertyValue) {
304
305         if (!isValidContainer(containerName)) {
306             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
307         }
308         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
309             throw new UnauthorizedException("User is not authorized to perform this operation on container "
310                     + containerName);
311         }
312         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
313         if (switchManager == null) {
314             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
315         }
316
317         handleNodeAvailability(containerName, nodeType, nodeId);
318         Node node = Node.fromString(nodeType, nodeId);
319         Property prop = switchManager.createProperty(propertyName, propertyValue);
320         if (prop == null) {
321             throw new ResourceNotFoundException("Property with name " + propertyName + " does not exist.");
322         }
323         SwitchConfig switchConfig = switchManager.getSwitchConfig(node.toString());
324         Map<String, Property> nodeProperties = (switchConfig == null) ? new HashMap<String, Property>()
325                 : new HashMap<String, Property>(switchConfig.getNodeProperties());
326         nodeProperties.put(prop.getName(), prop);
327         SwitchConfig newSwitchConfig = new SwitchConfig(node.toString(), nodeProperties);
328         Status status = switchManager.updateNodeConfig(newSwitchConfig);
329         if (status.isSuccess()) {
330             NorthboundUtils.auditlog("Property " + propertyName, username, "updated",
331                     "of Node " + NorthboundUtils.getNodeDesc(node, switchManager), containerName);
332
333             return Response.created(uriInfo.getRequestUri()).build();
334         }
335         return NorthboundUtils.getResponse(status);
336     }
337
338     /**
339      * Delete a property of a node
340      *
341      * @param containerName
342      *            Name of the Container (Eg. 'SliceRed')
343      * @param nodeType
344      *            Type of the node being programmed (Eg. 'OF')
345      * @param nodeId
346      *            Node Identifier as specified by
347      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
348      *            '00:00:00:00:00:03:01:02')
349      * @param propertyName
350      *            Name of the Property. Properties that can be deleted are
351      *            description, forwarding(only in default container) and tier.
352      * @return Response as dictated by the HTTP Response Status code
353      *
354      *         <pre>
355      *
356      * Example:
357      *
358      * Request URL:
359      * http://localhost:8080/controller/nb/v2/switchmanager/default/node/OF/00:00:00:00:00:00:00:03/property/forwarding
360      *
361      * </pre>
362      */
363
364     @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}")
365     @DELETE
366     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
367     @StatusCodes({ @ResponseCode(code = 204, condition = "Property removed successfully"),
368         @ResponseCode(code = 400, condition = "The nodeId or configuration is invalid"),
369         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
370         @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
371         @ResponseCode(code = 409, condition = "Unable to delete property due to cluster conflict"),
372         @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
373     public Response deleteNodeProperty(@PathParam("containerName") String containerName,
374             @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId,
375             @PathParam("propertyName") String propertyName) {
376
377         if (!isValidContainer(containerName)) {
378             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
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         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
385         if (switchManager == null) {
386             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
387         }
388
389         handleNodeAvailability(containerName, nodeType, nodeId);
390         Node node = Node.fromString(nodeType, nodeId);
391
392         SwitchConfig switchConfig = switchManager.getSwitchConfig(node.toString());
393         Status status;
394         if (switchConfig == null) {
395             status = new Status(StatusCode.NOTFOUND, "Switch Configuration does not exist");
396         } else {
397             Map<String, Property> nodeProperties = new HashMap<String, Property>(switchConfig.getNodeProperties());
398             if (!nodeProperties.containsKey(propertyName.toLowerCase())) {
399                 String msg = "Property " + propertyName + " does not exist or not configured for switch " + nodeId;
400                 status = new Status(StatusCode.NOTFOUND, msg);
401             } else {
402                 nodeProperties.remove(propertyName.toLowerCase());
403                 SwitchConfig newSwitchConfig = new SwitchConfig(node.toString(), nodeProperties);
404                 status = switchManager.updateNodeConfig(newSwitchConfig);
405                 if (status.isSuccess()) {
406                     NorthboundUtils.auditlog("Property " + propertyName, username, "removed", "of Node "
407                             + NorthboundUtils.getNodeDesc(node, switchManager), containerName);
408                     return Response.noContent().build();
409                 }
410             }
411         }
412         return NorthboundUtils.getResponse(status);
413     }
414
415     /**
416      * Get a property of a node
417      *
418      * @param containerName
419      *            Name of the Container (Eg. 'SliceRed')
420      * @param nodeType
421      *            Type of the node being programmed (Eg. 'OF')
422      * @param nodeId
423      *            Node Identifier as specified by
424      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
425      *            '00:00:00:00:00:03:01:02')
426      * @param propertyName
427      *            Name of the Property. Properties that can be deleted are
428      *            description, forwarding(only in default container) and tier.
429      * @return Property value of the property
430      *
431      *         <pre>
432      *
433      * Example:
434      *
435      * Request URL:
436      * http://localhost:8080/controller/nb/v2/switchmanager/default/node/OF/00:00:00:00:00:00:00:01/property/description
437      *
438      * Response body in XML
439      * &lt;description&gt;
440      *     &#x20;&#x20;&lt;value&gt;switch1&lt;/value&gt;
441      * &lt;/description&gt;
442      *
443      * Response body in JSON
444      * {
445      *     &#x20;&#x20;"value": "switch1"
446      * }
447      * </pre>
448      */
449
450     @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}")
451     @GET
452     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
453     @TypeHint(String.class)
454     @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
455         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
456         @ResponseCode(code = 404, condition = "The containerName is not found"),
457         @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
458     public Property getNodeProperty(@PathParam("containerName") String containerName,
459             @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId,
460             @PathParam("propertyName") String propertyName) {
461
462         if (!isValidContainer(containerName)) {
463             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
464         }
465         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
466             throw new UnauthorizedException("User is not authorized to perform this operation on container "
467                     + containerName);
468         }
469         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
470         if (switchManager == null) {
471             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
472         }
473
474         handleNodeAvailability(containerName, nodeType, nodeId);
475         Node node = Node.fromString(nodeType, nodeId);
476         if (node == null) {
477             throw new ResourceNotFoundException(nodeId + " : " + RestMessages.NONODE.toString());
478         }
479         SwitchConfig switchConfig = switchManager.getSwitchConfig(node.toString());
480         if (switchConfig == null) {
481             throw new ResourceNotFoundException(nodeId + " : " + "Config Not Found" );
482         } else {
483             Map<String, Property> nodeProperties = new HashMap<String, Property>(switchConfig.getNodeProperties());
484             if (!nodeProperties.containsKey(propertyName.toLowerCase())) {
485                 String msg = "Property " + propertyName + " does not exist or not "
486                         + "configured for switch " + nodeId;
487                 throw new ResourceNotFoundException(msg);
488             } else {
489                 return nodeProperties.get(propertyName.toLowerCase());
490             }
491         }
492     }
493
494     /**
495      *
496      * Retrieve a list of all the nodeconnectors and their properties in a given
497      * node
498      *
499      * @param containerName
500      *            The container for which we want to retrieve the list (Eg.
501      *            'default')
502      * @param nodeType
503      *            Type of the node being programmed (Eg. 'OF')
504      * @param nodeId
505      *            Node Identifier as specified by
506      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
507      *            '00:00:00:00:00:00:00:03')
508      * @return A List of Pair each pair represents a
509      *         {@link org.opendaylight.controller.sal.core.NodeConnector} and
510      *         its corresponding
511      *         {@link org.opendaylight.controller.sal.core.Property} attached to
512      *         it.
513      *
514      * <pre>
515      *
516      * Example:
517      *
518      * Request URL:
519      * http://localhost:8080/controller/nb/v2/switchmanager/default/node/OF/00:00:00:00:00:00:00:01
520      *
521      * Response body in XML:
522      * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
523      * &lt;list&gt;
524      *     &#x20;&#x20;&#x20;&lt;nodeConnectorProperties&gt;
525      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;nodeconnector&gt;
526      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;node&gt;
527      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;id&gt;00:00:00:00:00:00:00:01&lt;/id&gt;
528      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;type&gt;OF&lt;/type&gt;
529      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/node&gt;
530      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;id&gt;2&lt;/id&gt;
531      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;type&gt;OF&lt;/type&gt;
532      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/nodeconnector&gt;
533      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;properties&gt;
534      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;state&gt;
535      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;1&lt;/value&gt;
536      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/state&gt;
537      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;config&gt;
538      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;1&lt;/value&gt;
539      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/config&gt;
540      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;name&gt;
541      *                 &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;value&gt;L1_2-C2_1&lt;/value&gt;
542      *             &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/name&gt;
543      *         &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&lt;/properties&gt;
544      *     &#x20;&#x20;&#x20;&lt;/nodeConnectorProperties&gt;
545      * &lt;/list&gt;
546      *
547      * Response body in JSON:
548      * {
549      *    "nodeConnectorProperties":[
550      *       {
551      *          "nodeconnector":{
552      *             "node":{
553      *                "id":"00:00:00:00:00:00:00:01",
554      *                "type":"OF"
555      *             },
556      *             "id":"2",
557      *             "type":"OF"
558      *          },
559      *          "properties":{
560      *             "state":{
561      *                "value":"1"
562      *             },
563      *             "config":{
564      *                "value":"1"
565      *             },
566      *             "name":{
567      *                "value":"L1_2-C2_1"
568      *             }
569      *          }
570      *       }
571      *    ]
572      * }
573      *
574      * </pre>
575      */
576     @Path("/{containerName}/node/{nodeType}/{nodeId}")
577     @GET
578     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
579     @TypeHint(NodeConnectors.class)
580     @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"),
581         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
582         @ResponseCode(code = 404, condition = "The containerName is not found"),
583         @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable"),
584         @ResponseCode(code = 400, condition = "Incorrect query syntex") })
585     public NodeConnectors getNodeConnectors(@PathParam("containerName") String containerName,
586             @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId,
587             @QueryParam("_q") String queryString) {
588
589         if (!isValidContainer(containerName)) {
590             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
591         }
592         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
593             throw new UnauthorizedException("User is not authorized to perform this operation on container "
594                     + containerName);
595         }
596
597         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
598         if (switchManager == null) {
599             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
600         }
601
602         handleNodeAvailability(containerName, nodeType, nodeId);
603         Node node = Node.fromString(nodeType, nodeId);
604         List<NodeConnectorProperties> res = new ArrayList<NodeConnectorProperties>();
605         Set<NodeConnector> ncs = switchManager.getNodeConnectors(node);
606         if (ncs == null) {
607             return null;
608         }
609
610         for (NodeConnector nc : ncs) {
611             Map<String, Property> propMap = switchManager.getNodeConnectorProps(nc);
612             if (propMap == null) {
613                 continue;
614             }
615             Set<Property> props = new HashSet<Property>(propMap.values());
616             NodeConnectorProperties ncProps = new NodeConnectorProperties(nc, props);
617             res.add(ncProps);
618         }
619         NodeConnectors result = new NodeConnectors(res);
620         if (queryString != null) {
621             queryContext.createQuery(queryString, NodeConnectors.class)
622                 .filter(result, NodeConnectorProperties.class);
623         }
624         return result;
625     }
626
627     /**
628      * Add node-connector property to a node connector. This method returns a
629      * non-successful response if a node connector by the given name already
630      * exists.
631      *
632      * @param containerName
633      *            Name of the Container (Eg. 'default')
634      * @param nodeType
635      *            Type of the node being programmed (Eg. 'OF')
636      * @param nodeId
637      *            Node Identifier as specified by
638      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
639      *            '00:00:00:00:00:00:00:03')
640      * @param nodeConnectorType
641      *            Type of the node connector being programmed (Eg. 'OF')
642      * @param nodeConnectorId
643      *            NodeConnector Identifier as specified by
644      *            {@link org.opendaylight.controller.sal.core.NodeConnector}.
645      *            (Eg. '2'). If nodeConnecterId contains forward slash(/),
646      *            replace forward slash with underscore(_) in the URL. (Eg. for
647      *            Ethernet1/2, use Ethernet1_2)
648      * @param propertyName
649      *            Name of the Property specified by
650      *            {@link org.opendaylight.controller.sal.core.Property} and its
651      *            extended classes Property that can be configured is bandwidth
652      * @param propertyValue
653      *            Value of the Property specified by
654      *            {@link org.opendaylight.controller.sal.core.Property} and its
655      *            extended classes
656      * @return Response as dictated by the HTTP Response Status code
657      *
658      * <pre>
659      *
660      * Example:
661      *
662      * Request URL:
663      * http://localhost:8080/controller/nb/v2/switchmanager/default/nodeconnector/OF/00:00:00:00:00:00:00:01/OF/2/property/bandwidth/1
664      *
665      * </pre>
666      */
667
668     @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propertyName}/{propertyValue}")
669     @PUT
670     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
671     @StatusCodes({ @ResponseCode(code = 201, condition = "Operation successful"),
672         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
673         @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
674         @ResponseCode(code = 409, condition = "Unable to add property due to cluster conflict"),
675         @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
676     public Response addNodeConnectorProperty(@Context UriInfo uriInfo,
677             @PathParam("containerName") String containerName, @PathParam("nodeType") String nodeType,
678             @PathParam("nodeId") String nodeId, @PathParam("nodeConnectorType") String nodeConnectorType,
679             @PathParam("nodeConnectorId") String nodeConnectorId, @PathParam("propertyName") String propertyName,
680             @PathParam("propertyValue") String propertyValue) {
681
682         if (!isValidContainer(containerName)) {
683             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
684         }
685         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
686             throw new UnauthorizedException("User is not authorized to perform this operation on container "
687                     + containerName);
688         }
689
690         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
691         if (switchManager == null) {
692             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
693         }
694
695         handleNodeAvailability(containerName, nodeType, nodeId);
696         Node node = Node.fromString(nodeType, nodeId);
697
698         if (nodeConnectorId.contains("_")) {
699             nodeConnectorId = nodeConnectorId.replace("_", "/");
700         }
701
702         handleNodeConnectorAvailability(containerName, node, nodeConnectorType, nodeConnectorId);
703         NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorType, nodeConnectorId, node);
704
705         Property prop = switchManager.createProperty(propertyName, propertyValue);
706         if (prop == null) {
707             throw new ResourceNotFoundException(RestMessages.INVALIDDATA.toString());
708         }
709
710         Status ret = switchManager.addNodeConnectorProp(nc, prop);
711         if (ret.isSuccess()) {
712             NorthboundUtils.auditlog("Property " + propertyName, username, "updated", "of Node Connector "
713                     + NorthboundUtils.getPortName(nc, switchManager), containerName);
714             return Response.created(uriInfo.getRequestUri()).build();
715         }
716         throw new InternalServerErrorException(ret.getDescription());
717     }
718
719     /**
720      * Delete a property of a node connector
721      *
722      * @param containerName
723      *            Name of the Container (Eg. 'default')
724      * @param nodeType
725      *            Type of the node being programmed (Eg. 'OF')
726      * @param nodeId
727      *            Node Identifier as specified by
728      *            {@link org.opendaylight.controller.sal.core.Node} (Eg.
729      *            '00:00:00:00:00:00:00:01')
730      * @param nodeConnectorType
731      *            Type of the node connector being programmed (Eg. 'OF')
732      * @param nodeConnectorId
733      *            NodeConnector Identifier as specified by
734      *            {@link org.opendaylight.controller.sal.core.NodeConnector}
735      *            (Eg. '1'). If nodeConnecterId contains forward slash(/),
736      *            replace forward slash with underscore(_) in the URL. (Eg. for
737      *            Ethernet1/2, use Ethernet1_2)
738      * @param propertyName
739      *            Name of the Property specified by
740      *            {@link org.opendaylight.controller.sal.core.Property} and its
741      *            extended classes. Property that can be deleted is bandwidth
742      * @return Response as dictated by the HTTP Response Status code
743      *
744      * <pre>
745      *
746      * Example:
747      *
748      * Request URL:
749      * http://localhost:8080/controller/nb/v2/switchmanager/default/nodeconnector/OF/00:00:00:00:00:00:00:01/OF/2/property/bandwidth
750      *
751      * </pre>
752      */
753
754     @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propertyName}")
755     @DELETE
756     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
757     @StatusCodes({ @ResponseCode(code = 204, condition = "Property removed successfully"),
758         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
759         @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
760         @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
761     public Response deleteNodeConnectorProperty(@PathParam("containerName") String containerName,
762             @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId,
763             @PathParam("nodeConnectorType") String nodeConnectorType,
764             @PathParam("nodeConnectorId") String nodeConnectorId, @PathParam("propertyName") String propertyName) {
765
766         if (!isValidContainer(containerName)) {
767             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
768         }
769         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
770             throw new UnauthorizedException("User is not authorized to perform this operation on container "
771                     + containerName);
772         }
773
774         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
775         if (switchManager == null) {
776             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
777         }
778
779         handleNodeAvailability(containerName, nodeType, nodeId);
780         Node node = Node.fromString(nodeType, nodeId);
781
782         if (nodeConnectorId.contains("_")) {
783             nodeConnectorId = nodeConnectorId.replace("_", "/");
784         }
785
786         handleNodeConnectorAvailability(containerName, node, nodeConnectorType, nodeConnectorId);
787         NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorType, nodeConnectorId, node);
788         Status ret = switchManager.removeNodeConnectorProp(nc, propertyName);
789         if (ret.isSuccess()) {
790             NorthboundUtils.auditlog("Property " + propertyName, username, "removed", "of Node Connector "
791                     + NorthboundUtils.getPortName(nc, switchManager), containerName);
792             return Response.noContent().build();
793         }
794         throw new ResourceNotFoundException(ret.getDescription());
795     }
796
797     /**
798      * Save the current switch configurations
799      *
800      * @param containerName
801      *            Name of the Container (Eg. 'default')
802      * @return Response as dictated by the HTTP Response Status code
803      *
804      *         <pre>
805      *
806      * Example:
807      *
808      * Request URL:
809      * http://localhost:8080/controller/nb/v2/switchmanager/default/save
810      *
811      * </pre>
812      */
813     @Path("/{containerName}/save")
814     @POST
815     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
816     @StatusCodes({
817         @ResponseCode(code = 200, condition = "Operation successful"),
818         @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
819         @ResponseCode(code = 404, condition = "The containerName is not found"),
820         @ResponseCode(code = 500, condition = "Failed to save switch configuration. Failure Reason included in HTTP Error response"),
821         @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
822     public Response saveSwitchConfig(@PathParam("containerName") String containerName) {
823
824         if (!isValidContainer(containerName)) {
825             throw new ResourceNotFoundException("Container " + containerName + " does not exist.");
826         }
827         if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
828             throw new UnauthorizedException("User is not authorized to perform this operation on container "
829                     + containerName);
830         }
831         ISwitchManager switchManager = getIfSwitchManagerService(containerName);
832         if (switchManager == null) {
833             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
834         }
835
836         Status ret = switchManager.saveSwitchConfig();
837         if (ret.isSuccess()) {
838             return Response.ok().build();
839         }
840         throw new InternalServerErrorException(ret.getDescription());
841     }
842
843     private Node handleNodeAvailability(String containerName, String nodeType, String nodeId) {
844
845         Node node = Node.fromString(nodeType, nodeId);
846         if (node == null) {
847             throw new ResourceNotFoundException(nodeId + " : " + RestMessages.NONODE.toString());
848         }
849
850         ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
851
852         if (sm == null) {
853             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
854         }
855
856         if (!sm.getNodes().contains(node)) {
857             throw new ResourceNotFoundException(node.toString() + " : " + RestMessages.NONODE.toString());
858         }
859         return node;
860     }
861
862     private void handleNodeConnectorAvailability(String containerName, Node node, String nodeConnectorType,
863             String nodeConnectorId) {
864
865         NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorType, nodeConnectorId, node);
866         if (nc == null) {
867             throw new ResourceNotFoundException(nc + " : " + RestMessages.NORESOURCE.toString());
868         }
869
870         ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this);
871
872         if (sm == null) {
873             throw new ServiceUnavailableException("Switch Manager " + RestMessages.SERVICEUNAVAILABLE.toString());
874         }
875
876         if (!sm.getNodeConnectors(node).contains(nc)) {
877             throw new ResourceNotFoundException(nc.toString() + " : " + RestMessages.NORESOURCE.toString());
878         }
879     }
880
881     private boolean isValidContainer(String containerName) {
882         if (containerName.equals(GlobalConstants.DEFAULT.toString())) {
883             return true;
884         }
885         IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
886                 IContainerManager.class, this);
887         if (containerManager == null) {
888             throw new InternalServerErrorException(RestMessages.INTERNALERROR.toString());
889         }
890         if (containerManager.getContainerNames().contains(containerName)) {
891             return true;
892         }
893         return false;
894     }
895
896 }