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.openecomp.mso.openstack.utils;
24 import java.io.Serializable;
25 import java.util.ArrayList;
26 import java.util.Calendar;
27 import java.util.HashMap;
28 import java.util.List;
31 import org.openecomp.mso.cloud.CloudConfig;
32 import org.openecomp.mso.cloud.CloudConfigFactory;
33 import org.openecomp.mso.cloud.CloudIdentity;
34 import org.openecomp.mso.cloud.CloudSite;
35 import org.openecomp.mso.logger.MessageEnum;
36 import org.openecomp.mso.logger.MsoAlarmLogger;
37 import org.openecomp.mso.logger.MsoLogger;
38 import org.openecomp.mso.openstack.beans.NetworkInfo;
39 import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
40 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
41 import org.openecomp.mso.openstack.exceptions.MsoException;
42 import org.openecomp.mso.openstack.exceptions.MsoIOException;
43 import org.openecomp.mso.openstack.exceptions.MsoNetworkAlreadyExists;
44 import org.openecomp.mso.openstack.exceptions.MsoNetworkNotFound;
45 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
46 import org.openecomp.mso.openstack.exceptions.MsoTenantNotFound;
47 import com.woorea.openstack.base.client.OpenStackBaseException;
48 import com.woorea.openstack.base.client.OpenStackConnectException;
49 import com.woorea.openstack.base.client.OpenStackRequest;
50 import com.woorea.openstack.base.client.OpenStackResponseException;
51 import com.woorea.openstack.keystone.Keystone;
52 import com.woorea.openstack.keystone.model.Access;
53 import com.woorea.openstack.keystone.utils.KeystoneUtils;
54 import com.woorea.openstack.quantum.Quantum;
55 import com.woorea.openstack.quantum.model.Network;
56 import com.woorea.openstack.quantum.model.Networks;
57 import com.woorea.openstack.quantum.model.Segment;
58 import com.woorea.openstack.keystone.model.Authentication;
60 public class MsoNeutronUtils extends MsoCommonUtils
62 // Cache Neutron Clients statically. Since there is just one MSO user, there is no
63 // benefit to re-authentication on every request (or across different flows). The
64 // token will be used until it expires.
66 // The cache key is "tenantId:cloudId"
67 private static Map<String,NeutronCacheEntry> neutronClientCache = new HashMap<String,NeutronCacheEntry>();
69 // Fetch cloud configuration each time (may be cached in CloudConfig class)
70 private CloudConfig cloudConfig;
72 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
73 private String msoPropID;
75 public enum NetworkType {
76 BASIC, PROVIDER, MULTI_PROVIDER
79 public MsoNeutronUtils(String msoPropID, CloudConfigFactory cloudConfigFactory) {
80 cloudConfig = cloudConfigFactory.getCloudConfig();
81 this.msoPropID = msoPropID;
85 * Create a network with the specified parameters in the given cloud/tenant.
87 * If a network already exists with the same name, an exception will be thrown. Note that
88 * this is an MSO-imposed restriction. Openstack does not require uniqueness on network names.
90 * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
91 * @param tenantId The tenant in which to create the network
92 * @param type The type of network to create (Basic, Provider, Multi-Provider)
93 * @param networkName The network name to create
94 * @param provider The provider network name (for Provider or Multi-Provider networks)
95 * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
96 * @return a NetworkInfo object which describes the newly created network
97 * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
98 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
99 * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
101 public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
102 throws MsoException, MsoNetworkAlreadyExists, MsoCloudSiteNotFound
104 // Obtain the cloud site information where we will create the stack
105 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
106 if (cloudSite == null) {
107 throw new MsoCloudSiteNotFound(cloudSiteId);
110 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
112 // Check if a network already exists with this name
113 // Openstack will allow duplicate name, so require explicit check
114 Network network = findNetworkByName (neutronClient, networkName);
116 if (network != null) {
117 // Network already exists. Throw an exception
118 LOGGER.error(MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network already exists");
119 throw new MsoNetworkAlreadyExists (networkName, tenantId, cloudSiteId);
122 // Does not exist, create a new one
123 network = new Network();
124 network.setName(networkName);
125 network.setAdminStateUp(true);
127 if (type == NetworkType.PROVIDER) {
128 if (provider != null && vlans != null && vlans.size() > 0) {
129 network.setProviderPhysicalNetwork (provider);
130 network.setProviderNetworkType("vlan");
131 network.setProviderSegmentationId (vlans.get(0));
133 } else if (type == NetworkType.MULTI_PROVIDER) {
134 if (provider != null && vlans != null && vlans.size() > 0) {
135 List<Segment> segments = new ArrayList<Segment>(vlans.size());
136 for (int vlan : vlans) {
137 Segment segment = new Segment();
138 segment.setProviderPhysicalNetwork (provider);
139 segment.setProviderNetworkType("vlan");
140 segment.setProviderSegmentationId (vlan);
142 segments.add(segment);
144 network.setSegments(segments);
149 OpenStackRequest<Network> request = neutronClient.networks().create(network);
150 Network newNetwork = executeAndRecordOpenstackRequest(request);
151 return new NetworkInfo(newNetwork);
153 catch (OpenStackBaseException e) {
154 // Convert Neutron exception to an MsoOpenstackException
155 MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
158 catch (RuntimeException e) {
160 MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
167 * Query for a network with the specified name or ID in the given cloud. If the network exists,
168 * return an NetworkInfo object. If not, return null.
170 * Whenever possible, the network ID should be used as it is much more efficient. Query by
171 * name requires retrieval of all networks for the tenant and search for matching name.
173 * @param networkNameOrId The network to query
174 * @param tenantId The Openstack tenant to look in for the network
175 * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
176 * @return a NetworkInfo object describing the queried network, or null if not found
177 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
178 * @throws MsoCloudSiteNotFound
180 public NetworkInfo queryNetwork (String networkNameOrId, String tenantId, String cloudSiteId)
181 throws MsoException, MsoCloudSiteNotFound
183 LOGGER.debug("In queryNetwork");
185 // Obtain the cloud site information
186 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
187 if (cloudSite == null) {
188 throw new MsoCloudSiteNotFound(cloudSiteId);
191 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
193 // Check if the network exists and return its info
195 Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
196 if (network == null) {
197 LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
200 return new NetworkInfo(network);
202 catch (OpenStackBaseException e) {
203 // Convert Neutron exception to an MsoOpenstackException
204 MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
207 catch (RuntimeException e) {
209 MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
215 * Delete the specified Network (by ID) in the given cloud.
216 * If the network does not exist, success is returned.
218 * @param networkNameOrId The name or Openstack ID of the network to delete
219 * @param cloudId 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)
226 throws MsoException, MsoCloudSiteNotFound
228 // Obtain the cloud site information where we will create the stack
229 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
230 if (cloudSite == null) {
231 throw new MsoCloudSiteNotFound(cloudSiteId);
234 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
237 // Check that the network exists.
238 Network network = findNetworkById (neutronClient, networkId);
239 if (network == null) {
240 LOGGER.info(MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId, "Openstack", "");
244 OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
245 executeAndRecordOpenstackRequest(request);
247 LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
249 catch (OpenStackBaseException e) {
250 // Convert Neutron exception to an MsoOpenstackException
251 MsoException me = neutronExceptionToMsoException (e, "Delete Network");
254 catch (RuntimeException e) {
256 MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
265 * Update a network with the specified parameters in the given cloud/tenant.
267 * Specifically, this call is intended to update the VLAN segments on a
268 * multi-provider network. The provider segments will be replaced with the
269 * supplied list of VLANs.
271 * Note that updating the 'segments' array is not normally supported by Neutron.
272 * This method relies on a Platform Orchestration extension (using SDN controller
273 * to manage the virtual networking).
275 * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
276 * @param the Openstack ID of the tenant in which to update the network
277 * @param networkId The unique Openstack ID of the network to be updated
278 * @param type The network type (Basic, Provider, Multi-Provider)
279 * @param provider The provider network name. This should not change.
280 * @param vlans The list of VLAN segments to replace
281 * @return a NetworkInfo object which describes the updated network
282 * @throws MsoNetworkNotFound Thrown if the requested network does not exist
283 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
284 * @throws MsoCloudSiteNotFound
286 public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
287 throws MsoException, MsoNetworkNotFound, MsoCloudSiteNotFound
289 // Obtain the cloud site information where we will create the stack
290 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
291 if (cloudSite == null) {
292 throw new MsoCloudSiteNotFound(cloudSiteId);
294 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
296 // Check that the network exists
297 Network network = findNetworkById (neutronClient, networkId);
299 if (network == null) {
300 // Network not found. Throw an exception
301 LOGGER.error(MessageEnum.RA_NETWORK_NOT_FOUND, networkId, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network not found");
302 throw new MsoNetworkNotFound (networkId, tenantId, cloudSiteId);
305 // Overwrite the properties to be updated
306 if (type == NetworkType.PROVIDER) {
307 if (provider != null && vlans != null && vlans.size() > 0) {
308 network.setProviderPhysicalNetwork (provider);
309 network.setProviderNetworkType("vlan");
310 network.setProviderSegmentationId (vlans.get(0));
312 } else if (type == NetworkType.MULTI_PROVIDER) {
313 if (provider != null && vlans != null && vlans.size() > 0) {
314 List<Segment> segments = new ArrayList<Segment>(vlans.size());
315 for (int vlan : vlans) {
316 Segment segment = new Segment();
317 segment.setProviderPhysicalNetwork (provider);
318 segment.setProviderNetworkType("vlan");
319 segment.setProviderSegmentationId (vlan);
321 segments.add(segment);
323 network.setSegments(segments);
328 OpenStackRequest<Network> request = neutronClient.networks().update(network);
329 Network newNetwork = executeAndRecordOpenstackRequest(request);
330 return new NetworkInfo(newNetwork);
332 catch (OpenStackBaseException e) {
333 // Convert Neutron exception to an MsoOpenstackException
334 MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
337 catch (RuntimeException e) {
339 MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
345 // -------------------------------------------------------------------
346 // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
349 * Get a Neutron (Quantum) client for the Openstack Network service.
350 * This requires a 'member'-level userId + password, which will be retrieved from
351 * properties based on the specified cloud Id. The tenant in which to operate
352 * must also be provided.
354 * On successful authentication, the Quantum object will be cached for the
355 * tenantID + cloudId so that it can be reused without reauthenticating with
356 * Openstack every time.
358 * @param cloudSite - a cloud site definition
359 * @param tenantId - Openstack tenant ID
360 * @return an authenticated Quantum object
362 private Quantum getNeutronClient (CloudSite cloudSite, String tenantId)
363 throws MsoTenantNotFound, MsoException
365 String cloudId = cloudSite.getId();
367 // Check first in the cache of previously authorized clients
368 String cacheKey = cloudId + ":" + tenantId;
369 if (neutronClientCache.containsKey(cacheKey)) {
370 if (! neutronClientCache.get(cacheKey).isExpired()) {
371 LOGGER.debug ("Using Cached HEAT Client for " + cacheKey);
372 Quantum neutronClient = neutronClientCache.get(cacheKey).getNeutronClient();
373 return neutronClient;
376 // Token is expired. Remove it from cache.
377 neutronClientCache.remove(cacheKey);
378 LOGGER.debug ("Expired Cached Neutron Client for " + cacheKey);
382 // Obtain an MSO token for the tenant from the identity service
383 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
384 Keystone keystoneTenantClient = new Keystone (cloudIdentity.getKeystoneUrl(cloudId, msoPropID));
385 Access access = null;
387 Authentication credentials = cloudIdentity.getAuthentication ();
388 OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
389 access = executeAndRecordOpenstackRequest(request);
391 catch (OpenStackResponseException e) {
392 if (e.getStatus() == 401) {
393 // Authentication error.
394 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
395 alarmLogger .sendAlarm("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
396 throw new MsoAdapterException(error);
399 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
403 catch (OpenStackConnectException e) {
404 // Connection to Openstack failed
405 MsoIOException me = new MsoIOException (e.getMessage(), e);
406 me.addContext("TokenAuth");
409 catch (RuntimeException e) {
411 MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
415 String region = cloudSite.getRegionId();
416 String neutronUrl = null;
418 neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
419 if (! neutronUrl.endsWith("/")) {
420 neutronUrl += "/v2.0/";
422 } catch (RuntimeException e) {
423 // This comes back for not found (probably an incorrect region ID)
424 String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
425 alarmLogger.sendAlarm("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
426 throw new MsoAdapterException (error, e);
428 // This is needed for testing in the MT cloud
429 if (cloudId.equals("MT")) {
430 neutronUrl = neutronUrl.replace("controller", "mtdnj02bh01wt.bvoip.labs.att.com");
433 Quantum neutronClient = new Quantum(neutronUrl);
434 neutronClient.token(access.getToken().getId());
436 neutronClientCache.put(cacheKey, new NeutronCacheEntry(neutronUrl, access.getToken().getId(), access.getToken().getExpires()));
437 LOGGER.debug ("Caching Neutron Client for " + cacheKey);
439 return neutronClient;
443 * Forcibly expire a Neutron client from the cache. This call is for use by
444 * the KeystoneClient in case where a tenant is deleted. In that case,
445 * all cached credentials must be purged so that fresh authentication is
446 * done on subsequent calls.
451 public static void expireNeutronClient (String tenantId, String cloudId) {
452 String cacheKey = cloudId + ":" + tenantId;
453 if (neutronClientCache.containsKey(cacheKey)) {
454 neutronClientCache.remove(cacheKey);
455 LOGGER.debug ("Deleted Cached Neutron Client for " + cacheKey);
461 * Find a tenant (or query its existence) by its Name or Id. Check first against the
462 * ID. If that fails, then try by name.
464 * @param adminClient an authenticated Keystone object
465 * @param tenantName the tenant name or ID to query
466 * @return a Tenant object or null if not found
468 public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
470 if (networkNameOrId == null) {
474 Network network = findNetworkById(neutronClient, networkNameOrId);
476 if (network == null) {
477 network = findNetworkByName(neutronClient, networkNameOrId);
484 * Find a network (or query its existence) by its Id.
486 * @param neutronClient an authenticated Quantum object
487 * @param networkId the network ID to query
488 * @return a Network object or null if not found
490 private static Network findNetworkById (Quantum neutronClient, String networkId)
492 if (networkId == null) {
497 OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
498 Network network = executeAndRecordOpenstackRequest(request);
501 catch (OpenStackResponseException e) {
502 if (e.getStatus() == 404) {
505 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
512 * Find a network (or query its existence) by its Name. This method avoids an
513 * initial lookup by ID when it's known that we have the network Name.
515 * Neutron does not support 'name=*' query parameter for Network query (show).
516 * The only way to query by name is to retrieve all networks and look for the
517 * match. While inefficient, this capability will be provided as it is needed
518 * by MSO, but should be avoided in favor of ID whenever possible.
521 * Network names are not required to be unique, though MSO will attempt to enforce
522 * uniqueness. This call probably needs to return an error (instead of returning
525 * @param neutronClient an authenticated Quantum object
526 * @param networkName the network name to query
527 * @return a Network object or null if not found
529 public Network findNetworkByName (Quantum neutronClient, String networkName)
531 if (networkName == null) {
536 OpenStackRequest<Networks> request = neutronClient.networks().list();
537 Networks networks = executeAndRecordOpenstackRequest(request);
538 for (Network network : networks.getList()) {
539 if (network.getName().equals(networkName)) {
540 LOGGER.debug ("Found match on network name: " + networkName);
544 LOGGER.debug ("findNetworkByName - no match found for " + networkName);
547 catch (OpenStackResponseException e) {
548 if (e.getStatus() == 404) {
551 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");
559 * An entry in the Neutron Client Cache. It saves the Neutron client object
560 * along with the token expiration. After this interval, this cache
561 * item will no longer be used.
563 private static class NeutronCacheEntry implements Serializable
565 private static final long serialVersionUID = 1L;
567 private String neutronUrl;
568 private String token;
569 private Calendar expires;
571 public NeutronCacheEntry (String neutronUrl, String token, Calendar expires) {
572 this.neutronUrl = neutronUrl;
574 this.expires = expires;
577 public Quantum getNeutronClient () {
578 Quantum neutronClient = new Quantum(neutronUrl);
579 neutronClient.token(token);
580 return neutronClient;
583 public boolean isExpired() {
584 if (expires == null) {
588 Calendar now = Calendar.getInstance();
589 if (now.after(expires)) {
598 * Clean up the Neutron client cache to remove expired entries.
600 public static void neutronCacheCleanup () {
601 for (String cacheKey : neutronClientCache.keySet()) {
602 if (neutronClientCache.get(cacheKey).isExpired()) {
603 neutronClientCache.remove(cacheKey);
604 LOGGER.debug ("Cleaned Up Cached Neutron Client for " + cacheKey);
610 * Reset the Neutron client cache.
611 * This may be useful if cached credentials get out of sync.
613 public static void neutronCacheReset () {
614 neutronClientCache = new HashMap<String,NeutronCacheEntry>();