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;
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.ErrorCode;
39 import org.onap.so.logger.MessageEnum;
40 import org.onap.so.openstack.beans.NetworkInfo;
41 import org.onap.so.openstack.exceptions.MsoAdapterException;
42 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
43 import org.onap.so.openstack.exceptions.MsoException;
44 import org.onap.so.openstack.exceptions.MsoIOException;
45 import org.onap.so.openstack.exceptions.MsoNetworkAlreadyExists;
46 import org.onap.so.openstack.exceptions.MsoNetworkNotFound;
47 import org.onap.so.openstack.exceptions.MsoOpenstackException;
48 import org.onap.so.openstack.mappers.NetworkInfoMapper;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.beans.factory.annotation.Autowired;
52 import org.springframework.stereotype.Component;
53 import com.woorea.openstack.base.client.OpenStackBaseException;
54 import com.woorea.openstack.base.client.OpenStackConnectException;
55 import com.woorea.openstack.base.client.OpenStackRequest;
56 import com.woorea.openstack.base.client.OpenStackResponseException;
57 import com.woorea.openstack.keystone.Keystone;
58 import com.woorea.openstack.keystone.model.Access;
59 import com.woorea.openstack.keystone.model.Authentication;
60 import com.woorea.openstack.keystone.utils.KeystoneUtils;
61 import com.woorea.openstack.quantum.Quantum;
62 import com.woorea.openstack.quantum.model.Network;
63 import com.woorea.openstack.quantum.model.Networks;
64 import com.woorea.openstack.quantum.model.Port;
65 import com.woorea.openstack.quantum.model.Segment;
68 public class MsoNeutronUtils extends MsoCommonUtils {
70 // Fetch cloud configuration each time (may be cached in CloudConfig class)
72 private CloudConfig cloudConfig;
75 private AuthenticationMethodFactory authenticationMethodFactory;
78 private MsoTenantUtilsFactory tenantUtilsFactory;
81 private KeystoneV3Authentication keystoneV3Authentication;
83 private static Logger logger = LoggerFactory.getLogger(MsoNeutronUtils.class);
85 public enum NetworkType {
86 BASIC, PROVIDER, MULTI_PROVIDER
90 * Create a network with the specified parameters in the given cloud/tenant.
92 * If a network already exists with the same name, an exception will be thrown. Note that this is an MSO-imposed
93 * 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,
108 String provider, List<Integer> vlans) throws MsoException {
109 // Obtain the cloud site information where we will create the stack
110 CloudSite cloudSite =
111 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
113 Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
115 // Check if a network already exists with this name
116 // Openstack will allow duplicate name, so require explicit check
117 Network network = findNetworkByName(neutronClient, networkName);
119 if (network != null) {
120 // Network already exists. Throw an exception
121 logger.error("{} Network {} on Cloud site {} for tenant {} already exists {}",
122 MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId,
123 ErrorCode.DataError.getValue());
124 throw new MsoNetworkAlreadyExists(networkName, tenantId, cloudSiteId);
127 // Does not exist, create a new one
128 network = new Network();
129 network.setName(networkName);
130 network.setAdminStateUp(true);
132 if (type == NetworkType.PROVIDER) {
133 if (provider != null && vlans != null && vlans.size() > 0) {
134 network.setProviderPhysicalNetwork(provider);
135 network.setProviderNetworkType("vlan");
136 network.setProviderSegmentationId(vlans.get(0));
138 } else if (type == NetworkType.MULTI_PROVIDER) {
139 if (provider != null && vlans != null && vlans.size() > 0) {
140 List<Segment> segments = new ArrayList<>(vlans.size());
141 for (int vlan : vlans) {
142 Segment segment = new Segment();
143 segment.setProviderPhysicalNetwork(provider);
144 segment.setProviderNetworkType("vlan");
145 segment.setProviderSegmentationId(vlan);
147 segments.add(segment);
149 network.setSegments(segments);
154 OpenStackRequest<Network> request = neutronClient.networks().create(network);
155 Network newNetwork = executeAndRecordOpenstackRequest(request);
156 return new NetworkInfoMapper(newNetwork).map();
157 } catch (OpenStackBaseException e) {
158 // Convert Neutron exception to an MsoOpenstackException
159 MsoException me = neutronExceptionToMsoException(e, "CreateNetwork");
161 } catch (RuntimeException e) {
163 MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
170 * Query for a network with the specified name or ID in the given cloud. If the network exists, return an
171 * NetworkInfo object. If not, return null.
173 * Whenever possible, the network ID should be used as it is much more efficient. Query by name requires retrieval
174 * 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 {
185 logger.debug("In queryNetwork");
187 // Obtain the cloud site information
188 CloudSite cloudSite =
189 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> 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: {} not found in tenant {}", networkNameOrId, tenantId);
200 return new NetworkInfoMapper(network).map();
201 } catch (OpenStackBaseException e) {
202 // Convert Neutron exception to an MsoOpenstackException
203 MsoException me = neutronExceptionToMsoException(e, "QueryNetwork");
205 } catch (RuntimeException e) {
207 MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
212 public Optional<Port> getNeutronPort(String neutronPortId, String tenantId, String cloudSiteId) {
214 logger.debug("Finding Neutron port:" + neutronPortId);
215 CloudSite cloudSite =
216 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
217 Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
218 Port port = findPortById(neutronClient, neutronPortId);
220 return Optional.empty();
222 return Optional.of(port);
223 } catch (RuntimeException | MsoException e) {
224 logger.error("Error retrieving neutron port", e);
225 return Optional.empty();
230 * Delete the specified Network (by ID) in the given cloud. If the network does not exist, success is returned.
233 * @param networkId Openstack ID of the network to delete
234 * @param tenantId The Openstack tenant.
235 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the network.
236 * @return true if the network was deleted, false if the network did not exist
237 * @throws MsoOpenstackException If the Openstack API call returns an exception, this local exception will be
239 * @throws MsoCloudSiteNotFound
241 public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException {
242 // Obtain the cloud site information where we will create the stack
243 CloudSite cloudSite =
244 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
245 Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
248 // Check that the network exists.
249 Network network = findNetworkById(neutronClient, networkId);
250 if (network == null) {
251 logger.info("{} Network not found! Network id: {} Cloud site: {} Tenant: {} ",
252 MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId);
256 OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
257 executeAndRecordOpenstackRequest(request);
259 logger.debug("Deleted Network {} ({})", network.getId(), network.getName());
260 } catch (OpenStackBaseException e) {
261 // Convert Neutron exception to an MsoOpenstackException
262 MsoException me = neutronExceptionToMsoException(e, "Delete Network");
264 } catch (RuntimeException e) {
266 MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
275 * Update a network with the specified parameters in the given cloud/tenant.
277 * Specifically, this call is intended to update the VLAN segments on a multi-provider network. The provider
278 * segments will be replaced with the supplied list of VLANs.
280 * Note that updating the 'segments' array is not normally supported by Neutron. This method relies on a Platform
281 * Orchestration extension (using SDN controller to manage the virtual networking).
283 * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
284 * @param tenantId Openstack ID of the tenant in which to update the network
285 * @param networkId The unique Openstack ID of the network to be updated
286 * @param type The network type (Basic, Provider, Multi-Provider)
287 * @param provider The provider network name. This should not change.
288 * @param vlans The list of VLAN segments to replace
289 * @return a NetworkInfo object which describes the updated network
290 * @throws MsoNetworkNotFound Thrown if the requested network does not exist
291 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
292 * @throws MsoCloudSiteNotFound
294 public NetworkInfo updateNetwork(String cloudSiteId, String tenantId, String networkId, NetworkType type,
295 String provider, List<Integer> vlans) throws MsoException {
296 // Obtain the cloud site information where we will create the stack
297 CloudSite cloudSite =
298 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
299 Quantum neutronClient = getNeutronClient(cloudSite, tenantId);
301 // Check that the network exists
302 Network network = findNetworkById(neutronClient, networkId);
304 if (network == null) {
305 // Network not found. Throw an exception
306 logger.error("{} Network {} on Cloud site {} for Tenant {} not found {}", MessageEnum.RA_NETWORK_NOT_FOUND,
307 networkId, cloudSiteId, tenantId, ErrorCode.DataError.getValue());
308 throw new MsoNetworkNotFound(networkId, tenantId, cloudSiteId);
311 // Overwrite the properties to be updated
312 if (type == NetworkType.PROVIDER) {
313 if (provider != null && vlans != null && vlans.size() > 0) {
314 network.setProviderPhysicalNetwork(provider);
315 network.setProviderNetworkType("vlan");
316 network.setProviderSegmentationId(vlans.get(0));
318 } else if (type == NetworkType.MULTI_PROVIDER) {
319 if (provider != null && vlans != null && vlans.size() > 0) {
320 List<Segment> segments = new ArrayList<>(vlans.size());
321 for (int vlan : vlans) {
322 Segment segment = new Segment();
323 segment.setProviderPhysicalNetwork(provider);
324 segment.setProviderNetworkType("vlan");
325 segment.setProviderSegmentationId(vlan);
327 segments.add(segment);
329 network.setSegments(segments);
334 OpenStackRequest<Network> request = neutronClient.networks().update(network);
335 Network newNetwork = executeAndRecordOpenstackRequest(request);
336 return new NetworkInfoMapper(newNetwork).map();
337 } catch (OpenStackBaseException e) {
338 // Convert Neutron exception to an MsoOpenstackException
339 MsoException me = neutronExceptionToMsoException(e, "UpdateNetwork");
341 } catch (RuntimeException e) {
343 MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
349 // -------------------------------------------------------------------
350 // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
353 * Get a Neutron (Quantum) client for the Openstack Network service. This requires a 'member'-level userId +
354 * password, which will be retrieved from properties based on the specified cloud Id. The tenant in which to operate
355 * must also be provided.
357 * On successful authentication, the Quantum object will be cached for the tenantID + cloudId so that it can be
358 * reused without reauthenticating with Openstack every time.
360 * @param cloudSite - a cloud site definition
361 * @param tenantId - Openstack tenant ID
362 * @return an authenticated Quantum object
364 private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException {
365 String cloudId = cloudSite.getId();
366 String region = cloudSite.getRegionId();
369 // Obtain an MSO token for the tenant from the identity service
370 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
371 MsoTenantUtils tenantUtils =
372 tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
373 final String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
374 String neutronUrl = null;
375 String tokenId = null;
376 Calendar expiration = null;
378 if (ServerType.KEYSTONE.equals(cloudIdentity.getIdentityServerType())) {
379 Keystone keystoneTenantClient = new Keystone(keystoneUrl);
380 Access access = null;
382 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
383 OpenStackRequest<Access> request =
384 keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
385 access = executeAndRecordOpenstackRequest(request);
389 neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
390 if (!neutronUrl.endsWith("/")) {
391 neutronUrl += "/v2.0/";
393 } catch (RuntimeException e) {
394 // This comes back for not found (probably an incorrect region ID)
395 String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
396 throw new MsoAdapterException(error, e);
398 tokenId = access.getToken().getId();
399 expiration = access.getToken().getExpires();
400 } else if (ServerType.KEYSTONE_V3.equals(cloudIdentity.getIdentityServerType())) {
402 KeystoneAuthHolder holder = keystoneV3Authentication.getToken(cloudSite, tenantId, "network");
403 tokenId = holder.getId();
404 expiration = holder.getexpiration();
405 neutronUrl = holder.getServiceUrl();
406 if (!neutronUrl.endsWith("/")) {
407 neutronUrl += "/v2.0/";
409 } catch (ServiceEndpointNotFoundException e) {
410 // This comes back for not found (probably an incorrect region ID)
411 String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
412 throw new MsoAdapterException(error, e);
415 } catch (OpenStackResponseException e) {
416 if (e.getStatus() == 401) {
417 // Authentication error.
418 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
420 throw new MsoAdapterException(error);
422 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
425 } catch (OpenStackConnectException e) {
426 // Connection to Openstack failed
427 MsoIOException me = new MsoIOException(e.getMessage(), e);
428 me.addContext("TokenAuth");
430 } catch (RuntimeException e) {
432 MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
436 Quantum neutronClient = new Quantum(neutronUrl);
437 neutronClient.token(tokenId);
438 return neutronClient;
442 * Find a tenant (or query its existence) by its Name or Id. Check first against the ID. If that fails, then try by
445 * @param adminClient an authenticated Keystone object
447 * @param tenantName the tenant name or ID to query
449 * @return a Tenant object or null if not found
451 public Network findNetworkByNameOrId(Quantum neutronClient, String networkNameOrId) {
452 if (networkNameOrId == null) {
456 Network network = findNetworkById(neutronClient, networkNameOrId);
458 if (network == null) {
459 network = findNetworkByName(neutronClient, networkNameOrId);
466 * Find a network (or query its existence) by its Id.
468 * @param neutronClient an authenticated Quantum object
470 * @param networkId the network ID to query
472 * @return a Network object or null if not found
474 private Network findNetworkById(Quantum neutronClient, String networkId) {
475 if (networkId == null) {
480 OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
481 Network network = executeAndRecordOpenstackRequest(request);
483 } catch (OpenStackResponseException e) {
484 if (e.getStatus() == 404) {
487 logger.error("{} {} Openstack Error, GET Network By ID ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
488 ErrorCode.DataError.getValue(), networkId, e);
495 private Port findPortById(Quantum neutronClient, String neutronPortId) {
496 if (neutronPortId == null) {
501 OpenStackRequest<Port> request = neutronClient.ports().show(neutronPortId);
502 Port port = executeAndRecordOpenstackRequest(request);
504 } catch (OpenStackResponseException e) {
505 if (e.getStatus() == 404) {
506 logger.warn("Neutron port not found: " + neutronPortId, "Neutron port not found: " + neutronPortId);
509 logger.error("{} {} Openstack Error, GET Neutron Port By ID ({}): ",
510 MessageEnum.RA_CONNECTION_EXCEPTION, ErrorCode.DataError.getValue(), neutronPortId, e);
517 * Find a network (or query its existence) by its Name. This method avoids an initial lookup by ID when it's known
518 * that we have the network Name.
520 * Neutron does not support 'name=*' query parameter for Network query (show). The only way to query by name is to
521 * retrieve all networks and look for the match. While inefficient, this capability will be provided as it is needed
522 * by MSO, but should be avoided in favor of ID whenever possible.
524 * TODO: Network names are not required to be unique, though MSO will attempt to enforce uniqueness. This call
525 * probably needs to return an error (instead of returning the first match).
527 * @param neutronClient an authenticated Quantum object
529 * @param networkName the network name to query
531 * @return a Network object or null if not found
533 public Network findNetworkByName(Quantum neutronClient, String networkName) {
534 if (networkName == null) {
539 OpenStackRequest<Networks> request = neutronClient.networks().list();
540 Networks networks = executeAndRecordOpenstackRequest(request);
541 for (Network network : networks.getList()) {
542 if (network.getName().equals(networkName)) {
543 logger.debug("Found match on network name: {}", networkName);
547 logger.debug("findNetworkByName - no match found for {}", networkName);
549 } catch (OpenStackResponseException e) {
550 if (e.getStatus() == 404) {
553 logger.error("{} {} Openstack Error, GET Network By Name ({}): ", MessageEnum.RA_CONNECTION_EXCEPTION,
554 ErrorCode.DataError.getValue(), networkName, e);