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