Replaced all tabs with spaces in java and pom.xml
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / openstack / utils / MsoNeutronUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Modifications Copyright (c) 2019 Samsung
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.so.openstack.utils;
24
25
26 import java.util.ArrayList;
27 import java.util.Calendar;
28 import java.util.List;
29 import java.util.Optional;
30 import org.onap.so.cloud.CloudConfig;
31 import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
32 import org.onap.so.cloud.authentication.KeystoneAuthHolder;
33 import org.onap.so.cloud.authentication.KeystoneV3Authentication;
34 import org.onap.so.cloud.authentication.ServiceEndpointNotFoundException;
35 import org.onap.so.db.catalog.beans.CloudIdentity;
36 import org.onap.so.db.catalog.beans.CloudSite;
37 import org.onap.so.db.catalog.beans.ServerType;
38 import org.onap.so.logger.ErrorCode;
39 import org.onap.so.logger.MessageEnum;
40 import org.onap.so.openstack.beans.NetworkInfo;
41 import org.onap.so.openstack.exceptions.MsoAdapterException;
42 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
43 import org.onap.so.openstack.exceptions.MsoException;
44 import org.onap.so.openstack.exceptions.MsoIOException;
45 import org.onap.so.openstack.exceptions.MsoNetworkAlreadyExists;
46 import org.onap.so.openstack.exceptions.MsoNetworkNotFound;
47 import org.onap.so.openstack.exceptions.MsoOpenstackException;
48 import org.onap.so.openstack.mappers.NetworkInfoMapper;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.beans.factory.annotation.Autowired;
52 import org.springframework.stereotype.Component;
53 import com.woorea.openstack.base.client.OpenStackBaseException;
54 import com.woorea.openstack.base.client.OpenStackConnectException;
55 import com.woorea.openstack.base.client.OpenStackRequest;
56 import com.woorea.openstack.base.client.OpenStackResponseException;
57 import com.woorea.openstack.keystone.Keystone;
58 import com.woorea.openstack.keystone.model.Access;
59 import com.woorea.openstack.keystone.model.Authentication;
60 import com.woorea.openstack.keystone.utils.KeystoneUtils;
61 import com.woorea.openstack.quantum.Quantum;
62 import com.woorea.openstack.quantum.model.Network;
63 import com.woorea.openstack.quantum.model.Networks;
64 import com.woorea.openstack.quantum.model.Port;
65 import com.woorea.openstack.quantum.model.Segment;
66
67 @Component
68 public class MsoNeutronUtils extends MsoCommonUtils {
69
70     // Fetch cloud configuration each time (may be cached in CloudConfig class)
71     @Autowired
72     private CloudConfig cloudConfig;
73
74     @Autowired
75     private AuthenticationMethodFactory authenticationMethodFactory;
76
77     @Autowired
78     private MsoTenantUtilsFactory tenantUtilsFactory;
79
80     @Autowired
81     private KeystoneV3Authentication keystoneV3Authentication;
82
83     private static Logger logger = LoggerFactory.getLogger(MsoNeutronUtils.class);
84
85     public enum NetworkType {
86         BASIC, PROVIDER, MULTI_PROVIDER
87     };
88
89     /**
90      * Create a network with the specified parameters in the given cloud/tenant.
91      *
92      * If a network already exists with the same name, an exception will be thrown. Note that this is an MSO-imposed
93      * restriction. Openstack does not require uniqueness on network names.
94      * <p>
95      * 
96      * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
97      * @param tenantId The tenant in which to create the network
98      * @param type The type of network to create (Basic, Provider, Multi-Provider)
99      * @param networkName The network name to create
100      * @param provider The provider network name (for Provider or Multi-Provider networks)
101      * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
102      * @return a NetworkInfo object which describes the newly created network
103      * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
104      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
105      * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
106      */
107     public NetworkInfo createNetwork(String cloudSiteId, String tenantId, NetworkType type, String networkName,
108             String provider, List<Integer> vlans) throws MsoException {
109         // Obtain the cloud site information where we will create the stack
110         CloudSite cloudSite =
111                 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
112
113         Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
114
115         // Check if a network already exists with this name
116         // Openstack will allow duplicate name, so require explicit check
117         Network network = findNetworkByName(neutronClient, networkName);
118
119         if (network != null) {
120             // Network already exists. Throw an exception
121             logger.error("{} Network {} on Cloud site {} for tenant {} already exists {}",
122                     MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId,
123                     ErrorCode.DataError.getValue());
124             throw new MsoNetworkAlreadyExists(networkName, tenantId, cloudSiteId);
125         }
126
127         // Does not exist, create a new one
128         network = new Network();
129         network.setName(networkName);
130         network.setAdminStateUp(true);
131
132         if (type == NetworkType.PROVIDER) {
133             if (provider != null && vlans != null && vlans.size() > 0) {
134                 network.setProviderPhysicalNetwork(provider);
135                 network.setProviderNetworkType("vlan");
136                 network.setProviderSegmentationId(vlans.get(0));
137             }
138         } else if (type == NetworkType.MULTI_PROVIDER) {
139             if (provider != null && vlans != null && vlans.size() > 0) {
140                 List<Segment> segments = new ArrayList<>(vlans.size());
141                 for (int vlan : vlans) {
142                     Segment segment = new Segment();
143                     segment.setProviderPhysicalNetwork(provider);
144                     segment.setProviderNetworkType("vlan");
145                     segment.setProviderSegmentationId(vlan);
146
147                     segments.add(segment);
148                 }
149                 network.setSegments(segments);
150             }
151         }
152
153         try {
154             OpenStackRequest<Network> request = neutronClient.networks().create(network);
155             Network newNetwork = executeAndRecordOpenstackRequest(request);
156             return new NetworkInfoMapper(newNetwork).map();
157         } catch (OpenStackBaseException e) {
158             // Convert Neutron exception to an MsoOpenstackException
159             MsoException me = neutronExceptionToMsoException(e, "CreateNetwork");
160             throw me;
161         } catch (RuntimeException e) {
162             // Catch-all
163             MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
164             throw me;
165         }
166     }
167
168
169     /**
170      * Query for a network with the specified name or ID in the given cloud. If the network exists, return an
171      * NetworkInfo object. If not, return null.
172      * <p>
173      * Whenever possible, the network ID should be used as it is much more efficient. Query by name requires retrieval
174      * of all networks for the tenant and search for matching name.
175      * <p>
176      * 
177      * @param networkNameOrId The network to query
178      * @param tenantId The Openstack tenant to look in for the network
179      * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
180      * @return a NetworkInfo object describing the queried network, or null if not found
181      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
182      * @throws MsoCloudSiteNotFound
183      */
184     public NetworkInfo queryNetwork(String networkNameOrId, String tenantId, String cloudSiteId) throws MsoException {
185         logger.debug("In queryNetwork");
186
187         // Obtain the cloud site information
188         CloudSite cloudSite =
189                 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
190
191         Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
192
193         // Check if the network exists and return its info
194         try {
195             Network network = findNetworkByNameOrId(neutronClient, networkNameOrId);
196             if (network == null) {
197                 logger.debug("Query Network: {} not found in tenant {}", networkNameOrId, tenantId);
198                 return null;
199             }
200             return new NetworkInfoMapper(network).map();
201         } catch (OpenStackBaseException e) {
202             // Convert Neutron exception to an MsoOpenstackException
203             MsoException me = neutronExceptionToMsoException(e, "QueryNetwork");
204             throw me;
205         } catch (RuntimeException e) {
206             // Catch-all
207             MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
208             throw me;
209         }
210     }
211
212     public Optional<Port> getNeutronPort(String neutronPortId, String tenantId, String cloudSiteId) {
213         try {
214             logger.debug("Finding Neutron port:" + neutronPortId);
215             CloudSite cloudSite =
216                     cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
217             Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
218             Port port = findPortById(neutronClient, neutronPortId);
219             if (port == null) {
220                 return Optional.empty();
221             }
222             return Optional.of(port);
223         } catch (RuntimeException | MsoException e) {
224             logger.error("Error retrieving neutron port", e);
225             return Optional.empty();
226         }
227     }
228
229     /**
230      * Delete the specified Network (by ID) in the given cloud. If the network does not exist, success is returned.
231      * <p>
232      * 
233      * @param networkId Openstack ID of the network to delete
234      * @param tenantId The Openstack tenant.
235      * @param cloudSiteId The cloud identifier (may be a region) from which to delete the network.
236      * @return true if the network was deleted, false if the network did not exist
237      * @throws MsoOpenstackException If the Openstack API call returns an exception, this local exception will be
238      *         thrown.
239      * @throws MsoCloudSiteNotFound
240      */
241     public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException {
242         // Obtain the cloud site information where we will create the stack
243         CloudSite cloudSite =
244                 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
245         Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
246
247         try {
248             // Check that the network exists.
249             Network network = findNetworkById(neutronClient, networkId);
250             if (network == null) {
251                 logger.info("{} Network not found! Network id: {} Cloud site: {} Tenant: {} ",
252                         MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId);
253                 return false;
254             }
255
256             OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
257             executeAndRecordOpenstackRequest(request);
258
259             logger.debug("Deleted Network {} ({})", network.getId(), network.getName());
260         } catch (OpenStackBaseException e) {
261             // Convert Neutron exception to an MsoOpenstackException
262             MsoException me = neutronExceptionToMsoException(e, "Delete Network");
263             throw me;
264         } catch (RuntimeException e) {
265             // Catch-all
266             MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
267             throw me;
268         }
269
270         return true;
271     }
272
273
274     /**
275      * Update a network with the specified parameters in the given cloud/tenant.
276      *
277      * Specifically, this call is intended to update the VLAN segments on a multi-provider network. The provider
278      * segments will be replaced with the supplied list of VLANs.
279      * <p>
280      * Note that updating the 'segments' array is not normally supported by Neutron. This method relies on a Platform
281      * Orchestration extension (using SDN controller to manage the virtual networking).
282      *
283      * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
284      * @param tenantId Openstack ID of the tenant in which to update the network
285      * @param networkId The unique Openstack ID of the network to be updated
286      * @param type The network type (Basic, Provider, Multi-Provider)
287      * @param provider The provider network name. This should not change.
288      * @param vlans The list of VLAN segments to replace
289      * @return a NetworkInfo object which describes the updated network
290      * @throws MsoNetworkNotFound Thrown if the requested network does not exist
291      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
292      * @throws MsoCloudSiteNotFound
293      */
294     public NetworkInfo updateNetwork(String cloudSiteId, String tenantId, String networkId, NetworkType type,
295             String provider, List<Integer> vlans) throws MsoException {
296         // Obtain the cloud site information where we will create the stack
297         CloudSite cloudSite =
298                 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
299         Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
300
301         // Check that the network exists
302         Network network = findNetworkById(neutronClient, networkId);
303
304         if (network == null) {
305             // Network not found. Throw an exception
306             logger.error("{} Network {} on Cloud site {} for Tenant {} not found {}", MessageEnum.RA_NETWORK_NOT_FOUND,
307                     networkId, cloudSiteId, tenantId, ErrorCode.DataError.getValue());
308             throw new MsoNetworkNotFound(networkId, tenantId, cloudSiteId);
309         }
310
311         // Overwrite the properties to be updated
312         if (type == NetworkType.PROVIDER) {
313             if (provider != null && vlans != null && vlans.size() > 0) {
314                 network.setProviderPhysicalNetwork(provider);
315                 network.setProviderNetworkType("vlan");
316                 network.setProviderSegmentationId(vlans.get(0));
317             }
318         } else if (type == NetworkType.MULTI_PROVIDER) {
319             if (provider != null && vlans != null && vlans.size() > 0) {
320                 List<Segment> segments = new ArrayList<>(vlans.size());
321                 for (int vlan : vlans) {
322                     Segment segment = new Segment();
323                     segment.setProviderPhysicalNetwork(provider);
324                     segment.setProviderNetworkType("vlan");
325                     segment.setProviderSegmentationId(vlan);
326
327                     segments.add(segment);
328                 }
329                 network.setSegments(segments);
330             }
331         }
332
333         try {
334             OpenStackRequest<Network> request = neutronClient.networks().update(network);
335             Network newNetwork = executeAndRecordOpenstackRequest(request);
336             return new NetworkInfoMapper(newNetwork).map();
337         } catch (OpenStackBaseException e) {
338             // Convert Neutron exception to an MsoOpenstackException
339             MsoException me = neutronExceptionToMsoException(e, "UpdateNetwork");
340             throw me;
341         } catch (RuntimeException e) {
342             // Catch-all
343             MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
344             throw me;
345         }
346     }
347
348
349     // -------------------------------------------------------------------
350     // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
351
352     /**
353      * Get a Neutron (Quantum) client for the Openstack Network service. This requires a 'member'-level userId +
354      * password, which will be retrieved from properties based on the specified cloud Id. The tenant in which to operate
355      * must also be provided.
356      * <p>
357      * On successful authentication, the Quantum object will be cached for the tenantID + cloudId so that it can be
358      * reused without reauthenticating with Openstack every time.
359      *
360      * @param cloudSite - a cloud site definition
361      * @param tenantId - Openstack tenant ID
362      * @return an authenticated Quantum object
363      */
364     private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException {
365         String cloudId = cloudSite.getId();
366         String region = cloudSite.getRegionId();
367
368
369         // Obtain an MSO token for the tenant from the identity service
370         CloudIdentity cloudIdentity = cloudSite.getIdentityService();
371         MsoTenantUtils tenantUtils =
372                 tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
373         final String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
374         String neutronUrl = null;
375         String tokenId = null;
376         Calendar expiration = null;
377         try {
378             if (ServerType.KEYSTONE.equals(cloudIdentity.getIdentityServerType())) {
379                 Keystone keystoneTenantClient = new Keystone(keystoneUrl);
380                 Access access = null;
381
382                 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
383                 OpenStackRequest<Access> request =
384                         keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
385                 access = executeAndRecordOpenstackRequest(request);
386
387
388                 try {
389                     neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
390                     if (!neutronUrl.endsWith("/")) {
391                         neutronUrl += "/v2.0/";
392                     }
393                 } catch (RuntimeException e) {
394                     // This comes back for not found (probably an incorrect region ID)
395                     String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
396                     throw new MsoAdapterException(error, e);
397                 }
398                 tokenId = access.getToken().getId();
399                 expiration = access.getToken().getExpires();
400             } else if (ServerType.KEYSTONE_V3.equals(cloudIdentity.getIdentityServerType())) {
401                 try {
402                     KeystoneAuthHolder holder = keystoneV3Authentication.getToken(cloudSite, tenantId, "network");
403                     tokenId = holder.getId();
404                     expiration = holder.getexpiration();
405                     neutronUrl = holder.getServiceUrl();
406                     if (!neutronUrl.endsWith("/")) {
407                         neutronUrl += "/v2.0/";
408                     }
409                 } catch (ServiceEndpointNotFoundException e) {
410                     // This comes back for not found (probably an incorrect region ID)
411                     String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
412                     throw new MsoAdapterException(error, e);
413                 }
414             }
415         } catch (OpenStackResponseException e) {
416             if (e.getStatus() == 401) {
417                 // Authentication error.
418                 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
419
420                 throw new MsoAdapterException(error);
421             } else {
422                 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
423                 throw me;
424             }
425         } catch (OpenStackConnectException e) {
426             // Connection to Openstack failed
427             MsoIOException me = new MsoIOException(e.getMessage(), e);
428             me.addContext("TokenAuth");
429             throw me;
430         } catch (RuntimeException e) {
431             // Catch-all
432             MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
433             throw me;
434         }
435
436         Quantum neutronClient = new Quantum(neutronUrl);
437         neutronClient.token(tokenId);
438         return neutronClient;
439     }
440
441     /*
442      * Find a tenant (or query its existence) by its Name or Id. Check first against the ID. If that fails, then try by
443      * name.
444      *
445      * @param adminClient an authenticated Keystone object
446      * 
447      * @param tenantName the tenant name or ID to query
448      * 
449      * @return a Tenant object or null if not found
450      */
451     public Network findNetworkByNameOrId(Quantum neutronClient, String networkNameOrId) {
452         if (networkNameOrId == null) {
453             return null;
454         }
455
456         Network network = findNetworkById(neutronClient, networkNameOrId);
457
458         if (network == null) {
459             network = findNetworkByName(neutronClient, networkNameOrId);
460         }
461
462         return network;
463     }
464
465     /*
466      * Find a network (or query its existence) by its Id.
467      *
468      * @param neutronClient an authenticated Quantum object
469      * 
470      * @param networkId the network ID to query
471      * 
472      * @return a Network object or null if not found
473      */
474     private Network findNetworkById(Quantum neutronClient, String networkId) {
475         if (networkId == null) {
476             return null;
477         }
478
479         try {
480             OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
481             Network network = executeAndRecordOpenstackRequest(request);
482             return network;
483         } catch (OpenStackResponseException e) {
484             if (e.getStatus() == 404) {
485                 return null;
486             } else {
487                 logger.error("{} {} Openstack Error, GET Network By ID ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
488                         ErrorCode.DataError.getValue(), networkId, e);
489                 throw e;
490             }
491         }
492     }
493
494
495     private Port findPortById(Quantum neutronClient, String neutronPortId) {
496         if (neutronPortId == null) {
497             return null;
498         }
499
500         try {
501             OpenStackRequest<Port> request = neutronClient.ports().show(neutronPortId);
502             Port port = executeAndRecordOpenstackRequest(request);
503             return port;
504         } catch (OpenStackResponseException e) {
505             if (e.getStatus() == 404) {
506                 logger.warn("Neutron port not found: " + neutronPortId, "Neutron port not found: " + neutronPortId);
507                 return null;
508             } else {
509                 logger.error("{} {} Openstack Error, GET Neutron Port By ID ({}): ",
510                         MessageEnum.RA_CONNECTION_EXCEPTION, ErrorCode.DataError.getValue(), neutronPortId, e);
511                 throw e;
512             }
513         }
514     }
515
516     /*
517      * Find a network (or query its existence) by its Name. This method avoids an initial lookup by ID when it's known
518      * that we have the network Name.
519      *
520      * Neutron does not support 'name=*' query parameter for Network query (show). The only way to query by name is to
521      * retrieve all networks and look for the match. While inefficient, this capability will be provided as it is needed
522      * by MSO, but should be avoided in favor of ID whenever possible.
523      *
524      * TODO: Network names are not required to be unique, though MSO will attempt to enforce uniqueness. This call
525      * probably needs to return an error (instead of returning the first match).
526      *
527      * @param neutronClient an authenticated Quantum object
528      * 
529      * @param networkName the network name to query
530      * 
531      * @return a Network object or null if not found
532      */
533     public Network findNetworkByName(Quantum neutronClient, String networkName) {
534         if (networkName == null) {
535             return null;
536         }
537
538         try {
539             OpenStackRequest<Networks> request = neutronClient.networks().list();
540             Networks networks = executeAndRecordOpenstackRequest(request);
541             for (Network network : networks.getList()) {
542                 if (network.getName().equals(networkName)) {
543                     logger.debug("Found match on network name: {}", networkName);
544                     return network;
545                 }
546             }
547             logger.debug("findNetworkByName - no match found for {}", networkName);
548             return null;
549         } catch (OpenStackResponseException e) {
550             if (e.getStatus() == 404) {
551                 return null;
552             } else {
553                 logger.error("{} {} Openstack Error, GET Network By Name ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
554                         ErrorCode.DataError.getValue(), networkName, e);
555                 throw e;
556             }
557         }
558     }
559 }