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.Calendar;
26 import java.util.HashMap;
27 import java.util.List;
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;
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;
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;
68 public class MsoNeutronUtils extends MsoCommonUtils
71 // Fetch cloud configuration each time (may be cached in CloudConfig class)
73 private CloudConfig cloudConfig;
76 private AuthenticationMethodFactory authenticationMethodFactory;
79 private MsoTenantUtilsFactory tenantUtilsFactory;
82 private KeystoneV3Authentication keystoneV3Authentication;
84 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoNeutronUtils.class);
86 public enum NetworkType {
87 BASIC, PROVIDER, MULTI_PROVIDER
91 * Create a network with the specified parameters in the given cloud/tenant.
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.
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
107 public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
110 // Obtain the cloud site information where we will create the stack
111 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
112 () -> new MsoCloudSiteNotFound(cloudSiteId));
114 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
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);
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);
126 // Does not exist, create a new one
127 network = new Network();
128 network.setName(networkName);
129 network.setAdminStateUp(true);
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));
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);
146 segments.add(segment);
148 network.setSegments(segments);
153 OpenStackRequest<Network> request = neutronClient.networks().create(network);
154 Network newNetwork = executeAndRecordOpenstackRequest(request);
155 return new NetworkInfoMapper(newNetwork).map();
157 catch (OpenStackBaseException e) {
158 // Convert Neutron exception to an MsoOpenstackException
159 MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
162 catch (RuntimeException e) {
164 MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
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.
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.
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
184 public NetworkInfo queryNetwork(String networkNameOrId, String tenantId, String cloudSiteId) throws MsoException
186 LOGGER.debug("In queryNetwork");
188 // Obtain the cloud site information
189 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
190 () -> new MsoCloudSiteNotFound(cloudSiteId));
192 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
194 // Check if the network exists and return its info
196 Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
197 if (network == null) {
198 LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
201 return new NetworkInfoMapper(network).map();
203 catch (OpenStackBaseException e) {
204 // Convert Neutron exception to an MsoOpenstackException
205 MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
208 catch (RuntimeException e) {
210 MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
216 * Delete the specified Network (by ID) in the given cloud.
217 * If the network does not exist, success is returned.
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
227 public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException
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);
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", "");
242 OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
243 executeAndRecordOpenstackRequest(request);
245 LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
247 catch (OpenStackBaseException e) {
248 // Convert Neutron exception to an MsoOpenstackException
249 MsoException me = neutronExceptionToMsoException (e, "Delete Network");
252 catch (RuntimeException e) {
254 MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
263 * Update a network with the specified parameters in the given cloud/tenant.
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.
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).
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
284 public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
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);
292 // Check that the network exists
293 Network network = findNetworkById (neutronClient, networkId);
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);
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));
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);
317 segments.add(segment);
319 network.setSegments(segments);
324 OpenStackRequest<Network> request = neutronClient.networks().update(network);
325 Network newNetwork = executeAndRecordOpenstackRequest(request);
326 return new NetworkInfoMapper(newNetwork).map();
328 catch (OpenStackBaseException e) {
329 // Convert Neutron exception to an MsoOpenstackException
330 MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
333 catch (RuntimeException e) {
335 MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
341 // -------------------------------------------------------------------
342 // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
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.
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.
354 * @param cloudSite - a cloud site definition
355 * @param tenantId - Openstack tenant ID
356 * @return an authenticated Quantum object
358 private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException
360 String cloudId = cloudSite.getId();
361 String region = cloudSite.getRegionId();
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;
372 if (ServerType.KEYSTONE.equals(cloudIdentity.getIdentityServerType())) {
373 Keystone keystoneTenantClient = new Keystone(keystoneUrl);
374 Access access = null;
376 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
377 OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
378 access = executeAndRecordOpenstackRequest(request);
382 neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
383 if (! neutronUrl.endsWith("/")) {
384 neutronUrl += "/v2.0/";
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);
391 tokenId = access.getToken().getId();
392 expiration = access.getToken().getExpires();
393 } else if (ServerType.KEYSTONE_V3.equals(cloudIdentity.getIdentityServerType())) {
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/";
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);
409 catch (OpenStackResponseException e) {
410 if (e.getStatus() == 401) {
411 // Authentication error.
412 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
414 throw new MsoAdapterException(error);
417 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
421 catch (OpenStackConnectException e) {
422 // Connection to Openstack failed
423 MsoIOException me = new MsoIOException (e.getMessage(), e);
424 me.addContext("TokenAuth");
427 catch (RuntimeException e) {
429 MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
433 Quantum neutronClient = new Quantum(neutronUrl);
434 neutronClient.token(tokenId);
435 return neutronClient;
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.
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
446 public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
448 if (networkNameOrId == null) {
452 Network network = findNetworkById(neutronClient, networkNameOrId);
454 if (network == null) {
455 network = findNetworkByName(neutronClient, networkNameOrId);
462 * Find a network (or query its existence) by its Id.
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
468 private Network findNetworkById (Quantum neutronClient, String networkId)
470 if (networkId == null) {
475 OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
476 Network network = executeAndRecordOpenstackRequest(request);
479 catch (OpenStackResponseException e) {
480 if (e.getStatus() == 404) {
483 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
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.
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.
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
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
507 public Network findNetworkByName (Quantum neutronClient, String networkName)
509 if (networkName == null) {
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);
522 LOGGER.debug ("findNetworkByName - no match found for " + networkName);
525 catch (OpenStackResponseException e) {
526 if (e.getStatus() == 404) {
529 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");