2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.so.openstack.utils;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
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;
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;
63 public class MsoNeutronUtils extends MsoCommonUtils
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.
69 // The cache key is "tenantId:cloudId"
70 private static Map<String,NeutronCacheEntry> neutronClientCache = new HashMap<>();
72 // Fetch cloud configuration each time (may be cached in CloudConfig class)
74 private CloudConfig cloudConfig;
77 private AuthenticationMethodFactory authenticationMethodFactory;
80 private MsoTenantUtilsFactory tenantUtilsFactory;
82 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoNeutronUtils.class);
84 public enum NetworkType {
85 BASIC, PROVIDER, MULTI_PROVIDER
89 * Create a network with the specified parameters in the given cloud/tenant.
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.
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
105 public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
108 // Obtain the cloud site information where we will create the stack
109 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
110 () -> new MsoCloudSiteNotFound(cloudSiteId));
112 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
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);
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);
124 // Does not exist, create a new one
125 network = new Network();
126 network.setName(networkName);
127 network.setAdminStateUp(true);
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));
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);
144 segments.add(segment);
146 network.setSegments(segments);
151 OpenStackRequest<Network> request = neutronClient.networks().create(network);
152 Network newNetwork = executeAndRecordOpenstackRequest(request);
153 return new NetworkInfoMapper(newNetwork).map();
155 catch (OpenStackBaseException e) {
156 // Convert Neutron exception to an MsoOpenstackException
157 MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
160 catch (RuntimeException e) {
162 MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
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.
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.
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
182 public NetworkInfo queryNetwork(String networkNameOrId, String tenantId, String cloudSiteId) throws MsoException
184 LOGGER.debug("In queryNetwork");
186 // Obtain the cloud site information
187 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
188 () -> new MsoCloudSiteNotFound(cloudSiteId));
190 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
192 // Check if the network exists and return its info
194 Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
195 if (network == null) {
196 LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
199 return new NetworkInfoMapper(network).map();
201 catch (OpenStackBaseException e) {
202 // Convert Neutron exception to an MsoOpenstackException
203 MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
206 catch (RuntimeException e) {
208 MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
214 * Delete the specified Network (by ID) in the given cloud.
215 * If the network does not exist, success is returned.
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
225 public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException
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);
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", "");
240 OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
241 executeAndRecordOpenstackRequest(request);
243 LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
245 catch (OpenStackBaseException e) {
246 // Convert Neutron exception to an MsoOpenstackException
247 MsoException me = neutronExceptionToMsoException (e, "Delete Network");
250 catch (RuntimeException e) {
252 MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
261 * Update a network with the specified parameters in the given cloud/tenant.
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.
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).
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
282 public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
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);
290 // Check that the network exists
291 Network network = findNetworkById (neutronClient, networkId);
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);
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));
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);
315 segments.add(segment);
317 network.setSegments(segments);
322 OpenStackRequest<Network> request = neutronClient.networks().update(network);
323 Network newNetwork = executeAndRecordOpenstackRequest(request);
324 return new NetworkInfoMapper(newNetwork).map();
326 catch (OpenStackBaseException e) {
327 // Convert Neutron exception to an MsoOpenstackException
328 MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
331 catch (RuntimeException e) {
333 MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
339 // -------------------------------------------------------------------
340 // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
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.
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.
352 * @param cloudSite - a cloud site definition
353 * @param tenantId - Openstack tenant ID
354 * @return an authenticated Quantum object
356 private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException
358 String cloudId = cloudSite.getId();
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;
371 // Token is expired. Remove it from cache.
372 neutronClientCache.remove(cacheKey);
373 LOGGER.debug ("Expired Cached Neutron Client for " + cacheKey);
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;
384 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
385 OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
386 access = executeAndRecordOpenstackRequest(request);
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);
396 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
400 catch (OpenStackConnectException e) {
401 // Connection to Openstack failed
402 MsoIOException me = new MsoIOException (e.getMessage(), e);
403 me.addContext("TokenAuth");
406 catch (RuntimeException e) {
408 MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
412 String region = cloudSite.getRegionId();
413 String neutronUrl = null;
415 neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
416 if (! neutronUrl.endsWith("/")) {
417 neutronUrl += "/v2.0/";
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);
426 Quantum neutronClient = new Quantum(neutronUrl);
427 neutronClient.token(access.getToken().getId());
429 neutronClientCache.put(cacheKey, new NeutronCacheEntry(neutronUrl, access.getToken().getId(), access.getToken().getExpires()));
430 LOGGER.debug ("Caching Neutron Client for " + cacheKey);
432 return neutronClient;
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.
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);
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.
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
461 public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
463 if (networkNameOrId == null) {
467 Network network = findNetworkById(neutronClient, networkNameOrId);
469 if (network == null) {
470 network = findNetworkByName(neutronClient, networkNameOrId);
477 * Find a network (or query its existence) by its Id.
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
483 private Network findNetworkById (Quantum neutronClient, String networkId)
485 if (networkId == null) {
490 OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
491 Network network = executeAndRecordOpenstackRequest(request);
494 catch (OpenStackResponseException e) {
495 if (e.getStatus() == 404) {
498 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
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.
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.
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
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
522 public Network findNetworkByName (Quantum neutronClient, String networkName)
524 if (networkName == null) {
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);
537 LOGGER.debug ("findNetworkByName - no match found for " + networkName);
540 catch (OpenStackResponseException e) {
541 if (e.getStatus() == 404) {
544 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");