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