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