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