Initial push of Neutron interface
[controller.git] / opendaylight / northbound / networkconfiguration / neutron / src / main / java / org / opendaylight / controller / networkconfig / neutron / northbound / NeutronPortsNorthbound.java
1 /*\r
2  * Copyright IBM Corporation, 2013.  All rights reserved.\r
3  *\r
4  * This program and the accompanying materials are made available under the\r
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
6  * and is available at http://www.eclipse.org/legal/epl-v10.html\r
7  */\r
8 \r
9 package org.opendaylight.controller.networkconfig.neutron.northbound;\r
10 \r
11 import java.util.ArrayList;\r
12 import java.util.HashMap;\r
13 import java.util.Iterator;\r
14 import java.util.List;\r
15 import javax.ws.rs.Consumes;\r
16 import javax.ws.rs.DELETE;\r
17 import javax.ws.rs.GET;\r
18 import javax.ws.rs.POST;\r
19 import javax.ws.rs.PUT;\r
20 import javax.ws.rs.Path;\r
21 import javax.ws.rs.PathParam;\r
22 import javax.ws.rs.Produces;\r
23 import javax.ws.rs.QueryParam;\r
24 import javax.ws.rs.core.MediaType;\r
25 import javax.ws.rs.core.Response;\r
26 \r
27 import org.codehaus.enunciate.jaxrs.ResponseCode;\r
28 import org.codehaus.enunciate.jaxrs.StatusCodes;\r
29 import org.opendaylight.controller.networkconfig.neutron.INeutronNetworkCRUD;\r
30 import org.opendaylight.controller.networkconfig.neutron.INeutronPortAware;\r
31 import org.opendaylight.controller.networkconfig.neutron.INeutronPortCRUD;\r
32 import org.opendaylight.controller.networkconfig.neutron.INeutronSubnetAware;\r
33 import org.opendaylight.controller.networkconfig.neutron.INeutronSubnetCRUD;\r
34 import org.opendaylight.controller.networkconfig.neutron.NeutronPort;\r
35 import org.opendaylight.controller.networkconfig.neutron.NeutronSubnet;\r
36 import org.opendaylight.controller.networkconfig.neutron.Neutron_IPs;\r
37 import org.opendaylight.controller.northbound.commons.RestMessages;\r
38 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;\r
39 import org.opendaylight.controller.sal.utils.ServiceHelper;\r
40 \r
41 /**\r
42  * Open DOVE Northbound REST APIs.<br>\r
43  * This class provides REST APIs for managing the open DOVE\r
44  *\r
45  * <br>\r
46  * <br>\r
47  * Authentication scheme : <b>HTTP Basic</b><br>\r
48  * Authentication realm : <b>opendaylight</b><br>\r
49  * Transport : <b>HTTP and HTTPS</b><br>\r
50  * <br>\r
51  * HTTPS Authentication is disabled by default. Administrator can enable it in\r
52  * tomcat-server.xml after adding a proper keystore / SSL certificate from a\r
53  * trusted authority.<br>\r
54  * More info :\r
55  * http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration\r
56  *\r
57  */\r
58 \r
59 @Path("/ports")\r
60 public class NeutronPortsNorthbound {\r
61 \r
62     private NeutronPort extractFields(NeutronPort o, List<String> fields) {\r
63         return o.extractFields(fields);\r
64     }\r
65 \r
66     /**\r
67      * Returns a list of all Ports */\r
68 \r
69     @GET\r
70     @Produces({ MediaType.APPLICATION_JSON })\r
71     //@TypeHint(OpenStackPorts.class)\r
72     @StatusCodes({\r
73             @ResponseCode(code = 200, condition = "Operation successful"),\r
74             @ResponseCode(code = 401, condition = "Unauthorized"),\r
75             @ResponseCode(code = 501, condition = "Not Implemented") })\r
76     public Response listPorts(\r
77             // return fields\r
78             @QueryParam("fields") List<String> fields,\r
79             // note: openstack isn't clear about filtering on lists, so we aren't handling them\r
80             @QueryParam("id") String queryID,\r
81             @QueryParam("network_id") String queryNetworkID,\r
82             @QueryParam("name") String queryName,\r
83             @QueryParam("admin_state_up") String queryAdminStateUp,\r
84             @QueryParam("status") String queryStatus,\r
85             @QueryParam("mac_address") String queryMACAddress,\r
86             @QueryParam("device_id") String queryDeviceID,\r
87             @QueryParam("device_owner") String queryDeviceOwner,\r
88             @QueryParam("tenant_id") String queryTenantID,\r
89             // pagination\r
90             @QueryParam("limit") String limit,\r
91             @QueryParam("marker") String marker,\r
92             @QueryParam("page_reverse") String pageReverse\r
93             // sorting not supported\r
94             ) {\r
95         INeutronPortCRUD portInterface = NeutronNBInterfaces.getIfNBPortCRUD("default",this);\r
96         if (portInterface == null) {\r
97             throw new ServiceUnavailableException("Port CRUD Interface "\r
98                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
99         }\r
100         List<NeutronPort> allPorts = portInterface.getAllPorts();\r
101         List<NeutronPort> ans = new ArrayList<NeutronPort>();\r
102         Iterator<NeutronPort> i = allPorts.iterator();\r
103         while (i.hasNext()) {\r
104             NeutronPort oSS = i.next();\r
105             if ((queryID == null || queryID.equals(oSS.getID())) &&\r
106                     (queryNetworkID == null || queryNetworkID.equals(oSS.getNetworkUUID())) &&\r
107                     (queryName == null || queryName.equals(oSS.getName())) &&\r
108                     (queryAdminStateUp == null || queryAdminStateUp.equals(oSS.getAdminStateUp())) &&\r
109                     (queryStatus == null || queryStatus.equals(oSS.getStatus())) &&\r
110                     (queryMACAddress == null || queryMACAddress.equals(oSS.getMacAddress())) &&\r
111                     (queryDeviceID == null || queryDeviceID.equals(oSS.getDeviceID())) &&\r
112                     (queryDeviceOwner == null || queryDeviceOwner.equals(oSS.getDeviceOwner())) &&\r
113                     (queryTenantID == null || queryTenantID.equals(oSS.getTenantID()))) {\r
114                 if (fields.size() > 0)\r
115                     ans.add(extractFields(oSS,fields));\r
116                 else\r
117                     ans.add(oSS);\r
118             }\r
119         }\r
120         //TODO: apply pagination to results\r
121         return Response.status(200).entity(\r
122                 new NeutronPortRequest(ans)).build();\r
123     }\r
124 \r
125     /**\r
126      * Returns a specific Port */\r
127 \r
128     @Path("{portUUID}")\r
129     @GET\r
130     @Produces({ MediaType.APPLICATION_JSON })\r
131     //@TypeHint(OpenStackPorts.class)\r
132     @StatusCodes({\r
133             @ResponseCode(code = 200, condition = "Operation successful"),\r
134             @ResponseCode(code = 401, condition = "Unauthorized"),\r
135             @ResponseCode(code = 404, condition = "Not Found"),\r
136             @ResponseCode(code = 501, condition = "Not Implemented") })\r
137     public Response showPort(\r
138             @PathParam("portUUID") String portUUID,\r
139             // return fields\r
140             @QueryParam("fields") List<String> fields ) {\r
141         INeutronPortCRUD portInterface = NeutronNBInterfaces.getIfNBPortCRUD("default",this);\r
142         if (portInterface == null) {\r
143             throw new ServiceUnavailableException("Port CRUD Interface "\r
144                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
145         }\r
146         if (!portInterface.portExists(portUUID))\r
147             return Response.status(404).build();\r
148         if (fields.size() > 0) {\r
149             NeutronPort ans = portInterface.getPort(portUUID);\r
150             return Response.status(200).entity(\r
151                     new NeutronPortRequest(extractFields(ans, fields))).build();\r
152         } else\r
153             return Response.status(200).entity(\r
154                     new NeutronPortRequest(portInterface.getPort(portUUID))).build();\r
155     }\r
156 \r
157     /**\r
158      * Creates new Ports */\r
159 \r
160     @POST\r
161     @Produces({ MediaType.APPLICATION_JSON })\r
162     @Consumes({ MediaType.APPLICATION_JSON })\r
163     //@TypeHint(OpenStackPorts.class)\r
164     @StatusCodes({\r
165             @ResponseCode(code = 201, condition = "Created"),\r
166             @ResponseCode(code = 400, condition = "Bad Request"),\r
167             @ResponseCode(code = 401, condition = "Unauthorized"),\r
168             @ResponseCode(code = 403, condition = "Forbidden"),\r
169             @ResponseCode(code = 404, condition = "Not Found"),\r
170             @ResponseCode(code = 409, condition = "Conflict"),\r
171             @ResponseCode(code = 501, condition = "Not Implemented"),\r
172             @ResponseCode(code = 503, condition = "MAC generation failure") })\r
173     public Response createPorts(final NeutronPortRequest input) {\r
174         INeutronPortCRUD portInterface = NeutronNBInterfaces.getIfNBPortCRUD("default",this);\r
175         if (portInterface == null) {\r
176             throw new ServiceUnavailableException("Port CRUD Interface "\r
177                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
178         }\r
179         INeutronNetworkCRUD networkInterface = NeutronNBInterfaces.getIfNBNetworkCRUD("default", this);\r
180         if (networkInterface == null) {\r
181             throw new ServiceUnavailableException("Network CRUD Interface "\r
182                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
183         }\r
184         INeutronSubnetCRUD subnetInterface = NeutronNBInterfaces.getIfNBSubnetCRUD("default", this);\r
185         if (subnetInterface == null) {\r
186             throw new ServiceUnavailableException("Subnet CRUD Interface "\r
187                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
188         }\r
189         if (input.isSingleton()) {\r
190             NeutronPort singleton = input.getSingleton();\r
191 \r
192             /*\r
193              * the port must be part of an existing network, must not already exist,\r
194              * have a valid MAC and the MAC not be in use\r
195              */\r
196             if (singleton.getNetworkUUID() == null)\r
197                 return Response.status(400).build();\r
198             if (portInterface.portExists(singleton.getID()))\r
199                 return Response.status(400).build();\r
200             if (!networkInterface.networkExists(singleton.getNetworkUUID()))\r
201                 return Response.status(404).build();\r
202             if (singleton.getMacAddress() == null ||\r
203                     !singleton.getMacAddress().matches("^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$"))\r
204                 return Response.status(400).build();\r
205             if (portInterface.macInUse(singleton.getMacAddress()))\r
206                 return Response.status(409).build();\r
207             Object[] instances = ServiceHelper.getGlobalInstances(INeutronPortAware.class, this, null);\r
208             if (instances != null) {\r
209                 for (Object instance : instances) {\r
210                     INeutronPortAware service = (INeutronPortAware) instance;\r
211                     int status = service.canCreatePort(singleton);\r
212                     if (status < 200 || status > 299)\r
213                         return Response.status(status).build();\r
214                 }\r
215             }\r
216             /*\r
217              * if fixed IPs are specified, each one has to have an existing subnet ID\r
218              * that is in the same scoping network as the port.  In addition, if an IP\r
219              * address is specified it has to be a valid address for the subnet and not\r
220              * already in use\r
221              */\r
222             List<Neutron_IPs> fixedIPs = singleton.getFixedIPs();\r
223             if (fixedIPs != null && fixedIPs.size() > 0) {\r
224                 Iterator<Neutron_IPs> fixedIPIterator = fixedIPs.iterator();\r
225                 while (fixedIPIterator.hasNext()) {\r
226                     Neutron_IPs ip = fixedIPIterator.next();\r
227                     if (ip.getSubnetUUID() == null)\r
228                         return Response.status(400).build();\r
229                     if (!subnetInterface.subnetExists(ip.getSubnetUUID()))\r
230                         return Response.status(400).build();\r
231                     NeutronSubnet subnet = subnetInterface.getSubnet(ip.getSubnetUUID());\r
232                     if (!singleton.getNetworkUUID().equalsIgnoreCase(subnet.getNetworkUUID()))\r
233                         return Response.status(400).build();\r
234                     if (ip.getIpAddress() != null) {\r
235                         if (!subnet.isValidIP(ip.getIpAddress()))\r
236                             return Response.status(400).build();\r
237                         if (subnet.isIPInUse(ip.getIpAddress()))\r
238                             return Response.status(409).build();\r
239                     }\r
240                 }\r
241             }\r
242 \r
243             // add the port to the cache\r
244             portInterface.addPort(singleton);\r
245             if (instances != null) {\r
246                 for (Object instance : instances) {\r
247                     INeutronPortAware service = (INeutronPortAware) instance;\r
248                     service.neutronPortCreated(singleton);\r
249                 }\r
250             }\r
251         } else {\r
252             List<NeutronPort> bulk = input.getBulk();\r
253             Iterator<NeutronPort> i = bulk.iterator();\r
254             HashMap<String, NeutronPort> testMap = new HashMap<String, NeutronPort>();\r
255             Object[] instances = ServiceHelper.getGlobalInstances(INeutronSubnetAware.class, this, null);\r
256             while (i.hasNext()) {\r
257                 NeutronPort test = i.next();\r
258 \r
259                 /*\r
260                  * the port must be part of an existing network, must not already exist,\r
261                  * have a valid MAC and the MAC not be in use.  Further the bulk request\r
262                  * can't already contain a new port with the same UUID\r
263                  */\r
264                 if (portInterface.portExists(test.getID()))\r
265                     return Response.status(400).build();\r
266                 if (testMap.containsKey(test.getID()))\r
267                     return Response.status(400).build();\r
268                 for (NeutronPort check : testMap.values()) {\r
269                     if (test.getMacAddress().equalsIgnoreCase(check.getMacAddress()))\r
270                         return Response.status(409).build();\r
271                     for (Neutron_IPs test_fixedIP : test.getFixedIPs()) {\r
272                         for (Neutron_IPs check_fixedIP : check.getFixedIPs()) {\r
273                             if (test_fixedIP.getIpAddress().equals(check_fixedIP.getIpAddress()))\r
274                                 return Response.status(409).build();\r
275                         }\r
276                     }\r
277                 }\r
278                 testMap.put(test.getID(), test);\r
279                 if (!networkInterface.networkExists(test.getNetworkUUID()))\r
280                     return Response.status(404).build();\r
281                 if (!test.getMacAddress().matches("^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$"))\r
282                     return Response.status(400).build();\r
283                 if (portInterface.macInUse(test.getMacAddress()))\r
284                     return Response.status(409).build();\r
285                 if (instances != null) {\r
286                     for (Object instance : instances) {\r
287                         INeutronPortAware service = (INeutronPortAware) instance;\r
288                         int status = service.canCreatePort(test);\r
289                         if (status < 200 || status > 299)\r
290                             return Response.status(status).build();\r
291                     }\r
292                 }\r
293                 /*\r
294                  * if fixed IPs are specified, each one has to have an existing subnet ID\r
295                  * that is in the same scoping network as the port.  In addition, if an IP\r
296                  * address is specified it has to be a valid address for the subnet and not\r
297                  * already in use (or be the gateway IP address of the subnet)\r
298                  */\r
299                 List<Neutron_IPs> fixedIPs = test.getFixedIPs();\r
300                 if (fixedIPs != null && fixedIPs.size() > 0) {\r
301                     Iterator<Neutron_IPs> fixedIPIterator = fixedIPs.iterator();\r
302                     while (fixedIPIterator.hasNext()) {\r
303                         Neutron_IPs ip = fixedIPIterator.next();\r
304                         if (ip.getSubnetUUID() == null)\r
305                             return Response.status(400).build();\r
306                         if (!subnetInterface.subnetExists(ip.getSubnetUUID()))\r
307                             return Response.status(400).build();\r
308                         NeutronSubnet subnet = subnetInterface.getSubnet(ip.getSubnetUUID());\r
309                         if (!test.getNetworkUUID().equalsIgnoreCase(subnet.getNetworkUUID()))\r
310                             return Response.status(400).build();\r
311                         if (ip.getIpAddress() != null) {\r
312                             if (!subnet.isValidIP(ip.getIpAddress()))\r
313                                 return Response.status(400).build();\r
314                             //TODO: need to add consideration for a fixed IP being assigned the same address as a allocated IP in the\r
315                             //same bulk create\r
316                             if (subnet.isIPInUse(ip.getIpAddress()))\r
317                                 return Response.status(409).build();\r
318                         }\r
319                     }\r
320                 }\r
321             }\r
322 \r
323             //once everything has passed, then we can add to the cache\r
324             i = bulk.iterator();\r
325             while (i.hasNext()) {\r
326                 NeutronPort test = i.next();\r
327                 portInterface.addPort(test);\r
328                 if (instances != null) {\r
329                     for (Object instance : instances) {\r
330                         INeutronPortAware service = (INeutronPortAware) instance;\r
331                         service.neutronPortCreated(test);\r
332                     }\r
333                 }\r
334             }\r
335         }\r
336         return Response.status(201).entity(input).build();\r
337     }\r
338 \r
339     /**\r
340      * Updates a Port */\r
341 \r
342     @Path("{portUUID}")\r
343     @PUT\r
344     @Produces({ MediaType.APPLICATION_JSON })\r
345     @Consumes({ MediaType.APPLICATION_JSON })\r
346     //@TypeHint(OpenStackPorts.class)\r
347     @StatusCodes({\r
348             @ResponseCode(code = 200, condition = "Operation successful"),\r
349             @ResponseCode(code = 400, condition = "Bad Request"),\r
350             @ResponseCode(code = 401, condition = "Unauthorized"),\r
351             @ResponseCode(code = 403, condition = "Forbidden"),\r
352             @ResponseCode(code = 404, condition = "Not Found"),\r
353             @ResponseCode(code = 409, condition = "Conflict"),\r
354             @ResponseCode(code = 501, condition = "Not Implemented") })\r
355     public Response updatePort(\r
356             @PathParam("portUUID") String portUUID,\r
357             NeutronPortRequest input\r
358             ) {\r
359         INeutronPortCRUD portInterface = NeutronNBInterfaces.getIfNBPortCRUD("default",this);\r
360         if (portInterface == null) {\r
361             throw new ServiceUnavailableException("Port CRUD Interface "\r
362                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
363         }\r
364         INeutronSubnetCRUD subnetInterface = NeutronNBInterfaces.getIfNBSubnetCRUD("default", this);\r
365         if (subnetInterface == null) {\r
366             throw new ServiceUnavailableException("Subnet CRUD Interface "\r
367                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
368         }\r
369 \r
370         // port has to exist and only a single delta is supported\r
371         if (!portInterface.portExists(portUUID))\r
372             return Response.status(404).build();\r
373         NeutronPort target = portInterface.getPort(portUUID);\r
374         if (!input.isSingleton())\r
375             return Response.status(400).build();\r
376         NeutronPort singleton = input.getSingleton();\r
377         NeutronPort original = portInterface.getPort(portUUID);\r
378 \r
379         // deltas restricted by Neutron\r
380         if (singleton.getID() != null || singleton.getTenantID() != null ||\r
381                 singleton.getStatus() != null)\r
382             return Response.status(400).build();\r
383 \r
384         Object[] instances = ServiceHelper.getGlobalInstances(INeutronPortAware.class, this, null);\r
385         if (instances != null) {\r
386             for (Object instance : instances) {\r
387                 INeutronPortAware service = (INeutronPortAware) instance;\r
388                 int status = service.canUpdatePort(singleton, original);\r
389                 if (status < 200 || status > 299)\r
390                     return Response.status(status).build();\r
391             }\r
392         }\r
393 \r
394         // Verify the new fixed ips are valid\r
395         List<Neutron_IPs> fixedIPs = singleton.getFixedIPs();\r
396         if (fixedIPs != null && fixedIPs.size() > 0) {\r
397             Iterator<Neutron_IPs> fixedIPIterator = fixedIPs.iterator();\r
398             while (fixedIPIterator.hasNext()) {\r
399                 Neutron_IPs ip = fixedIPIterator.next();\r
400                 if (ip.getSubnetUUID() == null)\r
401                     return Response.status(400).build();\r
402                 if (!subnetInterface.subnetExists(ip.getSubnetUUID()))\r
403                     return Response.status(400).build();\r
404                 NeutronSubnet subnet = subnetInterface.getSubnet(ip.getSubnetUUID());\r
405                 if (!target.getNetworkUUID().equalsIgnoreCase(subnet.getNetworkUUID()))\r
406                     return Response.status(400).build();\r
407                 if (ip.getIpAddress() != null) {\r
408                     if (!subnet.isValidIP(ip.getIpAddress()))\r
409                         return Response.status(400).build();\r
410                     if (subnet.isIPInUse(ip.getIpAddress()))\r
411                         return Response.status(409).build();\r
412                 }\r
413             }\r
414         }\r
415 \r
416 //        TODO: Support change of security groups\r
417         // update the port and return the modified object\r
418         portInterface.updatePort(portUUID, singleton);\r
419         NeutronPort updatedPort = portInterface.getPort(portUUID);\r
420         if (instances != null) {\r
421             for (Object instance : instances) {\r
422                 INeutronPortAware service = (INeutronPortAware) instance;\r
423                 service.neutronPortUpdated(updatedPort);\r
424             }\r
425         }\r
426         return Response.status(200).entity(\r
427                 new NeutronPortRequest(updatedPort)).build();\r
428 \r
429     }\r
430 \r
431     /**\r
432      * Deletes a Port */\r
433 \r
434     @Path("{portUUID}")\r
435     @DELETE\r
436     @StatusCodes({\r
437         @ResponseCode(code = 204, condition = "No Content"),\r
438         @ResponseCode(code = 401, condition = "Unauthorized"),\r
439         @ResponseCode(code = 403, condition = "Forbidden"),\r
440         @ResponseCode(code = 404, condition = "Not Found"),\r
441         @ResponseCode(code = 501, condition = "Not Implemented") })\r
442     public Response deletePort(\r
443             @PathParam("portUUID") String portUUID) {\r
444         INeutronPortCRUD portInterface = NeutronNBInterfaces.getIfNBPortCRUD("default",this);\r
445         if (portInterface == null) {\r
446             throw new ServiceUnavailableException("Port CRUD Interface "\r
447                     + RestMessages.SERVICEUNAVAILABLE.toString());\r
448         }\r
449 \r
450         // port has to exist and not be owned by anyone.  then it can be removed from the cache\r
451         if (!portInterface.portExists(portUUID))\r
452             return Response.status(404).build();\r
453         NeutronPort port = portInterface.getPort(portUUID);\r
454         if (port.getDeviceID() != null ||\r
455                 port.getDeviceOwner() != null)\r
456             Response.status(403).build();\r
457         NeutronPort singleton = portInterface.getPort(portUUID);\r
458         Object[] instances = ServiceHelper.getGlobalInstances(INeutronPortAware.class, this, null);\r
459         if (instances != null) {\r
460             for (Object instance : instances) {\r
461                 INeutronPortAware service = (INeutronPortAware) instance;\r
462                 int status = service.canDeletePort(singleton);\r
463                 if (status < 200 || status > 299)\r
464                     return Response.status(status).build();\r
465             }\r
466         }\r
467         portInterface.removePort(portUUID);\r
468         if (instances != null) {\r
469             for (Object instance : instances) {\r
470                 INeutronPortAware service = (INeutronPortAware) instance;\r
471                 service.neutronPortDeleted(singleton);\r
472             }\r
473         }\r
474         return Response.status(204).build();\r
475     }\r
476 }\r