2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright (c) 2019 Samsung
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.so.openstack.utils;
26 import java.util.ArrayList;
27 import java.util.Calendar;
28 import java.util.List;
29 import java.util.Optional;
31 import org.onap.so.cloud.CloudConfig;
32 import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
33 import org.onap.so.cloud.authentication.KeystoneAuthHolder;
34 import org.onap.so.cloud.authentication.KeystoneV3Authentication;
35 import org.onap.so.cloud.authentication.ServiceEndpointNotFoundException;
36 import org.onap.so.db.catalog.beans.CloudIdentity;
37 import org.onap.so.db.catalog.beans.CloudSite;
38 import org.onap.so.db.catalog.beans.ServerType;
39 import org.onap.so.logger.ErrorCode;
40 import org.onap.so.logger.MessageEnum;
41 import org.onap.so.openstack.beans.NetworkInfo;
42 import org.onap.so.openstack.exceptions.MsoAdapterException;
43 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
44 import org.onap.so.openstack.exceptions.MsoException;
45 import org.onap.so.openstack.exceptions.MsoIOException;
46 import org.onap.so.openstack.exceptions.MsoNetworkAlreadyExists;
47 import org.onap.so.openstack.exceptions.MsoNetworkNotFound;
48 import org.onap.so.openstack.exceptions.MsoOpenstackException;
49 import org.onap.so.openstack.mappers.NetworkInfoMapper;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52 import org.springframework.beans.factory.annotation.Autowired;
53 import org.springframework.stereotype.Component;
55 import com.woorea.openstack.base.client.OpenStackBaseException;
56 import com.woorea.openstack.base.client.OpenStackConnectException;
57 import com.woorea.openstack.base.client.OpenStackRequest;
58 import com.woorea.openstack.base.client.OpenStackResponseException;
59 import com.woorea.openstack.keystone.Keystone;
60 import com.woorea.openstack.keystone.model.Access;
61 import com.woorea.openstack.keystone.model.Authentication;
62 import com.woorea.openstack.keystone.utils.KeystoneUtils;
63 import com.woorea.openstack.quantum.Quantum;
64 import com.woorea.openstack.quantum.model.Network;
65 import com.woorea.openstack.quantum.model.Networks;
66 import com.woorea.openstack.quantum.model.Port;
67 import com.woorea.openstack.quantum.model.Segment;
70 public class MsoNeutronUtils extends MsoCommonUtils
73 // Fetch cloud configuration each time (may be cached in CloudConfig class)
75 private CloudConfig cloudConfig;
78 private AuthenticationMethodFactory authenticationMethodFactory;
81 private MsoTenantUtilsFactory tenantUtilsFactory;
84 private KeystoneV3Authentication keystoneV3Authentication;
86 private static Logger logger = LoggerFactory.getLogger(MsoNeutronUtils.class);
88 public enum NetworkType {
89 BASIC, PROVIDER, MULTI_PROVIDER
93 * Create a network with the specified parameters in the given cloud/tenant.
95 * If a network already exists with the same name, an exception will be thrown. Note that
96 * this is an MSO-imposed restriction. Openstack does not require uniqueness on network names.
98 * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
99 * @param tenantId The tenant in which to create the network
100 * @param type The type of network to create (Basic, Provider, Multi-Provider)
101 * @param networkName The network name to create
102 * @param provider The provider network name (for Provider or Multi-Provider networks)
103 * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
104 * @return a NetworkInfo object which describes the newly created network
105 * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
106 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
107 * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
109 public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
112 // Obtain the cloud site information where we will create the stack
113 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
114 () -> new MsoCloudSiteNotFound(cloudSiteId));
116 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
118 // Check if a network already exists with this name
119 // Openstack will allow duplicate name, so require explicit check
120 Network network = findNetworkByName (neutronClient, networkName);
122 if (network != null) {
123 // Network already exists. Throw an exception
124 logger.error("{} Network {} on Cloud site {} for tenant {} already exists {}",
125 MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId,
126 ErrorCode.DataError.getValue());
127 throw new MsoNetworkAlreadyExists (networkName, tenantId, cloudSiteId);
130 // Does not exist, create a new one
131 network = new Network();
132 network.setName(networkName);
133 network.setAdminStateUp(true);
135 if (type == NetworkType.PROVIDER) {
136 if (provider != null && vlans != null && vlans.size() > 0) {
137 network.setProviderPhysicalNetwork (provider);
138 network.setProviderNetworkType("vlan");
139 network.setProviderSegmentationId (vlans.get(0));
141 } else if (type == NetworkType.MULTI_PROVIDER) {
142 if (provider != null && vlans != null && vlans.size() > 0) {
143 List<Segment> segments = new ArrayList<>(vlans.size());
144 for (int vlan : vlans) {
145 Segment segment = new Segment();
146 segment.setProviderPhysicalNetwork (provider);
147 segment.setProviderNetworkType("vlan");
148 segment.setProviderSegmentationId (vlan);
150 segments.add(segment);
152 network.setSegments(segments);
157 OpenStackRequest<Network> request = neutronClient.networks().create(network);
158 Network newNetwork = executeAndRecordOpenstackRequest(request);
159 return new NetworkInfoMapper(newNetwork).map();
161 catch (OpenStackBaseException e) {
162 // Convert Neutron exception to an MsoOpenstackException
163 MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
166 catch (RuntimeException e) {
168 MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
175 * Query for a network with the specified name or ID in the given cloud. If the network exists,
176 * return an NetworkInfo object. If not, return null.
178 * Whenever possible, the network ID should be used as it is much more efficient. Query by
179 * name requires retrieval of all networks for the tenant and search for matching name.
181 * @param networkNameOrId The network to query
182 * @param tenantId The Openstack tenant to look in for the network
183 * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
184 * @return a NetworkInfo object describing the queried network, or null if not found
185 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
186 * @throws MsoCloudSiteNotFound
188 public NetworkInfo queryNetwork(String networkNameOrId, String tenantId, String cloudSiteId) throws MsoException
190 logger.debug("In queryNetwork");
192 // Obtain the cloud site information
193 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
194 () -> new MsoCloudSiteNotFound(cloudSiteId));
196 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
198 // Check if the network exists and return its info
200 Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
201 if (network == null) {
202 logger.debug("Query Network: {} not found in tenant {}", networkNameOrId, tenantId);
205 return new NetworkInfoMapper(network).map();
207 catch (OpenStackBaseException e) {
208 // Convert Neutron exception to an MsoOpenstackException
209 MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
212 catch (RuntimeException e) {
214 MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
219 public Optional<Port> getNeutronPort(String neutronPortId, String tenantId, String cloudSiteId)
222 logger.debug("Finding Neutron port:" + neutronPortId);
223 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
224 () -> new MsoCloudSiteNotFound(cloudSiteId));
225 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
226 Port port = findPortById (neutronClient, neutronPortId);
228 return Optional.empty();
230 return Optional.of(port);
232 catch (RuntimeException | MsoException e) {
233 logger.error("Error retrieving neutron port", e);
234 return Optional.empty();
239 * Delete the specified Network (by ID) in the given cloud.
240 * If the network does not exist, success is returned.
242 * @param networkId Openstack ID of the network to delete
243 * @param tenantId The Openstack tenant.
244 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the network.
245 * @return true if the network was deleted, false if the network did not exist
246 * @throws MsoOpenstackException If the Openstack API call returns an exception, this local
247 * exception will be thrown.
248 * @throws MsoCloudSiteNotFound
250 public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException
252 // Obtain the cloud site information where we will create the stack
253 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
254 () -> new MsoCloudSiteNotFound(cloudSiteId));
255 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
258 // Check that the network exists.
259 Network network = findNetworkById (neutronClient, networkId);
260 if (network == null) {
261 logger.info("{} Network not found! Network id: {} Cloud site: {} Tenant: {} ",
262 MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId);
266 OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
267 executeAndRecordOpenstackRequest(request);
269 logger.debug("Deleted Network {} ({})", network.getId(), network.getName());
271 catch (OpenStackBaseException e) {
272 // Convert Neutron exception to an MsoOpenstackException
273 MsoException me = neutronExceptionToMsoException (e, "Delete Network");
276 catch (RuntimeException e) {
278 MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
287 * Update a network with the specified parameters in the given cloud/tenant.
289 * Specifically, this call is intended to update the VLAN segments on a
290 * multi-provider network. The provider segments will be replaced with the
291 * supplied list of VLANs.
293 * Note that updating the 'segments' array is not normally supported by Neutron.
294 * This method relies on a Platform Orchestration extension (using SDN controller
295 * to manage the virtual networking).
297 * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
298 * @param tenantId Openstack ID of the tenant in which to update the network
299 * @param networkId The unique Openstack ID of the network to be updated
300 * @param type The network type (Basic, Provider, Multi-Provider)
301 * @param provider The provider network name. This should not change.
302 * @param vlans The list of VLAN segments to replace
303 * @return a NetworkInfo object which describes the updated network
304 * @throws MsoNetworkNotFound Thrown if the requested network does not exist
305 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
306 * @throws MsoCloudSiteNotFound
308 public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
311 // Obtain the cloud site information where we will create the stack
312 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
313 () -> new MsoCloudSiteNotFound(cloudSiteId));
314 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
316 // Check that the network exists
317 Network network = findNetworkById (neutronClient, networkId);
319 if (network == null) {
320 // Network not found. Throw an exception
321 logger.error("{} Network {} on Cloud site {} for Tenant {} not found {}", MessageEnum.RA_NETWORK_NOT_FOUND,
322 networkId, cloudSiteId, tenantId, ErrorCode.DataError.getValue());
323 throw new MsoNetworkNotFound (networkId, tenantId, cloudSiteId);
326 // Overwrite the properties to be updated
327 if (type == NetworkType.PROVIDER) {
328 if (provider != null && vlans != null && vlans.size() > 0) {
329 network.setProviderPhysicalNetwork (provider);
330 network.setProviderNetworkType("vlan");
331 network.setProviderSegmentationId (vlans.get(0));
333 } else if (type == NetworkType.MULTI_PROVIDER) {
334 if (provider != null && vlans != null && vlans.size() > 0) {
335 List<Segment> segments = new ArrayList<>(vlans.size());
336 for (int vlan : vlans) {
337 Segment segment = new Segment();
338 segment.setProviderPhysicalNetwork (provider);
339 segment.setProviderNetworkType("vlan");
340 segment.setProviderSegmentationId (vlan);
342 segments.add(segment);
344 network.setSegments(segments);
349 OpenStackRequest<Network> request = neutronClient.networks().update(network);
350 Network newNetwork = executeAndRecordOpenstackRequest(request);
351 return new NetworkInfoMapper(newNetwork).map();
353 catch (OpenStackBaseException e) {
354 // Convert Neutron exception to an MsoOpenstackException
355 MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
358 catch (RuntimeException e) {
360 MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
366 // -------------------------------------------------------------------
367 // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
370 * Get a Neutron (Quantum) client for the Openstack Network service.
371 * This requires a 'member'-level userId + password, which will be retrieved from
372 * properties based on the specified cloud Id. The tenant in which to operate
373 * must also be provided.
375 * On successful authentication, the Quantum object will be cached for the
376 * tenantID + cloudId so that it can be reused without reauthenticating with
377 * Openstack every time.
379 * @param cloudSite - a cloud site definition
380 * @param tenantId - Openstack tenant ID
381 * @return an authenticated Quantum object
383 private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException
385 String cloudId = cloudSite.getId();
386 String region = cloudSite.getRegionId();
389 // Obtain an MSO token for the tenant from the identity service
390 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
391 MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
392 final String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
393 String neutronUrl = null;
394 String tokenId = null;
395 Calendar expiration = null;
397 if (ServerType.KEYSTONE.equals(cloudIdentity.getIdentityServerType())) {
398 Keystone keystoneTenantClient = new Keystone(keystoneUrl);
399 Access access = null;
401 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
402 OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
403 access = executeAndRecordOpenstackRequest(request);
407 neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
408 if (! neutronUrl.endsWith("/")) {
409 neutronUrl += "/v2.0/";
411 } catch (RuntimeException e) {
412 // This comes back for not found (probably an incorrect region ID)
413 String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
414 throw new MsoAdapterException (error, e);
416 tokenId = access.getToken().getId();
417 expiration = access.getToken().getExpires();
418 } else if (ServerType.KEYSTONE_V3.equals(cloudIdentity.getIdentityServerType())) {
420 KeystoneAuthHolder holder = keystoneV3Authentication.getToken(cloudSite, tenantId, "network");
421 tokenId = holder.getId();
422 expiration = holder.getexpiration();
423 neutronUrl = holder.getServiceUrl();
424 if (! neutronUrl.endsWith("/")) {
425 neutronUrl += "/v2.0/";
427 } catch (ServiceEndpointNotFoundException e) {
428 // This comes back for not found (probably an incorrect region ID)
429 String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
430 throw new MsoAdapterException (error, e);
434 catch (OpenStackResponseException e) {
435 if (e.getStatus() == 401) {
436 // Authentication error.
437 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
439 throw new MsoAdapterException(error);
442 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
446 catch (OpenStackConnectException e) {
447 // Connection to Openstack failed
448 MsoIOException me = new MsoIOException (e.getMessage(), e);
449 me.addContext("TokenAuth");
452 catch (RuntimeException e) {
454 MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
458 Quantum neutronClient = new Quantum(neutronUrl);
459 neutronClient.token(tokenId);
460 return neutronClient;
464 * Find a tenant (or query its existence) by its Name or Id. Check first against the
465 * ID. If that fails, then try by name.
467 * @param adminClient an authenticated Keystone object
468 * @param tenantName the tenant name or ID to query
469 * @return a Tenant object or null if not found
471 public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
473 if (networkNameOrId == null) {
477 Network network = findNetworkById(neutronClient, networkNameOrId);
479 if (network == null) {
480 network = findNetworkByName(neutronClient, networkNameOrId);
487 * Find a network (or query its existence) by its Id.
489 * @param neutronClient an authenticated Quantum object
490 * @param networkId the network ID to query
491 * @return a Network object or null if not found
493 private Network findNetworkById (Quantum neutronClient, String networkId)
495 if (networkId == null) {
500 OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
501 Network network = executeAndRecordOpenstackRequest(request);
504 catch (OpenStackResponseException e) {
505 if (e.getStatus() == 404) {
508 logger.error("{} {} Openstack Error, GET Network By ID ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
509 ErrorCode.DataError.getValue(), networkId, e);
516 private Port findPortById (Quantum neutronClient, String neutronPortId)
518 if (neutronPortId == null) {
523 OpenStackRequest<Port> request = neutronClient.ports().show(neutronPortId);
524 Port port = executeAndRecordOpenstackRequest(request);
527 catch (OpenStackResponseException e) {
528 if (e.getStatus() == 404) {
529 logger.warn("Neutron port not found: " + neutronPortId,"Neutron port not found: " + neutronPortId);
532 logger.error("{} {} Openstack Error, GET Neutron Port By ID ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
533 ErrorCode.DataError.getValue(), neutronPortId, e);
540 * Find a network (or query its existence) by its Name. This method avoids an
541 * initial lookup by ID when it's known that we have the network Name.
543 * Neutron does not support 'name=*' query parameter for Network query (show).
544 * The only way to query by name is to retrieve all networks and look for the
545 * match. While inefficient, this capability will be provided as it is needed
546 * by MSO, but should be avoided in favor of ID whenever possible.
549 * Network names are not required to be unique, though MSO will attempt to enforce
550 * uniqueness. This call probably needs to return an error (instead of returning
553 * @param neutronClient an authenticated Quantum object
554 * @param networkName the network name to query
555 * @return a Network object or null if not found
557 public Network findNetworkByName (Quantum neutronClient, String networkName)
559 if (networkName == null) {
564 OpenStackRequest<Networks> request = neutronClient.networks().list();
565 Networks networks = executeAndRecordOpenstackRequest(request);
566 for (Network network : networks.getList()) {
567 if (network.getName().equals(networkName)) {
568 logger.debug("Found match on network name: {}", networkName);
572 logger.debug("findNetworkByName - no match found for {}", networkName);
575 catch (OpenStackResponseException e) {
576 if (e.getStatus() == 404) {
579 logger.error("{} {} Openstack Error, GET Network By Name ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
580 ErrorCode.DataError.getValue(), networkName, e);