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