heatbridge implementation for openstack-adapter
[so.git] / adapters / mso-openstack-adapters / src / main / java / org / onap / so / heatbridge / HeatBridgeImpl.java
1 /*
2  * Copyright (C) 2018 Bell Canada. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onap.so.heatbridge;
17
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.function.Function;
24 import java.util.function.Predicate;
25 import java.util.stream.Collectors;
26
27 import javax.annotation.Nonnull;
28 import javax.ws.rs.WebApplicationException;
29
30 import org.apache.commons.collections.CollectionUtils;
31 import org.apache.commons.validator.routines.InetAddressValidator;
32 import org.onap.aai.domain.yang.Flavor;
33 import org.onap.aai.domain.yang.Image;
34 import org.onap.aai.domain.yang.L3InterfaceIpv4AddressList;
35 import org.onap.aai.domain.yang.LInterface;
36 import org.onap.aai.domain.yang.PInterface;
37 import org.onap.aai.domain.yang.SriovPf;
38 import org.onap.aai.domain.yang.SriovPfs;
39 import org.onap.aai.domain.yang.SriovVf;
40 import org.onap.aai.domain.yang.SriovVfs;
41 import org.onap.aai.domain.yang.Vlan;
42 import org.onap.aai.domain.yang.Vlans;
43 import org.onap.aai.domain.yang.Vserver;
44 import org.onap.so.client.aai.AAIObjectType;
45 import org.onap.so.client.aai.AAIResourcesClient;
46 import org.onap.so.client.aai.AAISingleTransactionClient;
47 import org.onap.so.client.aai.entities.uri.AAIResourceUri;
48 import org.onap.so.client.aai.entities.uri.AAIUriFactory;
49 import org.onap.so.client.graphinventory.entities.uri.Depth;
50 import org.onap.so.client.graphinventory.exceptions.BulkProcessFailed;
51 import org.onap.so.db.catalog.beans.CloudIdentity;
52 import org.onap.so.heatbridge.constants.HeatBridgeConstants;
53 import org.onap.so.heatbridge.factory.MsoCloudClientFactoryImpl;
54 import org.onap.so.heatbridge.helpers.AaiHelper;
55 import org.onap.so.heatbridge.openstack.api.OpenstackClient;
56 import org.onap.so.heatbridge.openstack.factory.OpenstackClientFactoryImpl;
57 import org.onap.so.heatbridge.utils.HeatBridgeUtils;
58 import org.onap.so.logger.MessageEnum;
59 import org.onap.so.logger.ErrorCode;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62 import org.openstack4j.model.compute.Server;
63 import org.openstack4j.model.heat.Resource;
64 import org.openstack4j.model.network.IP;
65 import org.openstack4j.model.network.Network;
66 import org.openstack4j.model.network.NetworkType;
67 import org.openstack4j.model.network.Port;
68
69 import com.google.common.base.Preconditions;
70 import com.google.common.base.Strings;
71 import com.google.common.collect.ImmutableMap;
72
73 /**
74  * This class provides an implementation of {@link HeatBridgeApi}
75  */
76 public class HeatBridgeImpl implements HeatBridgeApi {
77
78     private static final Logger logger = LoggerFactory.getLogger(HeatBridgeImpl.class);
79     private static final String ERR_MSG_NULL_OS_CLIENT = "Initialization error: Null openstack client. Authenticate with Keystone first.";
80     private static final String OOB_MGT_NETWORK_IDENTIFIER = "Management";
81     private OpenstackClient osClient;
82     private AAIResourcesClient resourcesClient;
83     private AAISingleTransactionClient transaction;
84     private String cloudOwner;
85     private String cloudRegionId;
86     private String tenantId;
87     private AaiHelper aaiHelper = new AaiHelper();
88     private CloudIdentity cloudIdentity;
89
90
91     public HeatBridgeImpl(AAIResourcesClient resourcesClient, final CloudIdentity cloudIdentity,
92         @Nonnull final String cloudOwner, @Nonnull final String cloudRegionId, @Nonnull final String tenantId) {
93         Objects.requireNonNull(cloudOwner, "Null cloud-owner value!");
94         Objects.requireNonNull(cloudRegionId, "Null cloud-region identifier!");
95         Objects.requireNonNull(tenantId, "Null tenant identifier!");
96         Objects.requireNonNull(tenantId, "Null AAI actions list!");
97
98         this.cloudIdentity = cloudIdentity;
99         this.cloudOwner = cloudOwner;
100         this.cloudRegionId = cloudRegionId;
101         this.tenantId = tenantId;
102         this.resourcesClient = resourcesClient;
103         this.transaction = resourcesClient.beginSingleTransaction();
104     }
105
106     @Override
107     public OpenstackClient authenticate() throws HeatBridgeException {
108         this.osClient = new MsoCloudClientFactoryImpl(new OpenstackClientFactoryImpl())
109             .getOpenstackClient(cloudIdentity.getIdentityUrl(), cloudIdentity.getMsoId(), cloudIdentity.getMsoPass(), cloudRegionId, tenantId);
110         logger.debug("Successfully authenticated with keystone for tenant: " + tenantId + " and cloud "
111             + "region: " + cloudRegionId);
112         return osClient;
113     }
114
115     @Override
116     public List<Resource> queryNestedHeatStackResources(final String heatStackId) {
117         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
118         Preconditions.checkState(!Strings.isNullOrEmpty(heatStackId), "Invalid heatStackId!");
119         List<Resource> stackBasedResources = osClient
120             .getStackBasedResources(heatStackId, HeatBridgeConstants.OS_DEFAULT_HEAT_NESTING);
121         logger.debug(stackBasedResources.size() + " heat stack resources are extracted for stack: " + heatStackId);
122         return stackBasedResources;
123     }
124
125     @Override
126     public List<String> extractStackResourceIdsByResourceType(final List<Resource> stackResources,
127         final String resourceType) {
128         return stackResources.stream()
129             .filter(stackResource -> stackResource.getType().equals(resourceType))
130             .map(Resource::getPhysicalResourceId)
131             .collect(Collectors.toList());
132     }
133
134     @Override
135     public List<String> extractNetworkIds(final List<String> networkNameList) {
136         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
137         return networkNameList.stream()
138             .map(netName -> osClient.listNetworksByFilter(ImmutableMap.of(HeatBridgeConstants.OS_NAME_KEY, netName)))
139             .filter(nets -> nets != null && nets.size() == 1) //extract network-id only if network-name is unique
140             .map(nets -> nets.get(0).getId())
141             .collect(Collectors.toList());
142     }
143
144     @Override
145     public List<Server> getAllOpenstackServers(final List<Resource> stackResources) {
146         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
147
148         // Filter Openstack Compute resources
149         List<String> serverIds = extractStackResourceIdsByResourceType(stackResources,
150             HeatBridgeConstants.OS_SERVER_RESOURCE_TYPE);
151         return serverIds.stream().map(serverId -> osClient.getServerById(serverId)).collect(Collectors.toList());
152     }
153
154     @Override
155     public List<org.openstack4j.model.compute.Image> extractOpenstackImagesFromServers(final List<Server> servers) {
156         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
157         return servers.stream().map(Server::getImage)
158             .filter(distinctByProperty(org.openstack4j.model.compute.Image::getId)).collect(Collectors.toList());
159     }
160
161     @Override
162     public List<org.openstack4j.model.compute.Flavor> extractOpenstackFlavorsFromServers(final List<Server> servers) {
163         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
164         return servers.stream().map(Server::getFlavor)
165             .filter(distinctByProperty(org.openstack4j.model.compute.Flavor::getId)).collect(Collectors.toList());
166     }
167
168     @Override
169     public void buildAddImagesToAaiAction(final List<org.openstack4j.model.compute.Image> images)
170         throws HeatBridgeException {
171         for (org.openstack4j.model.compute.Image image : images) {
172             Image aaiImage = aaiHelper.buildImage(image);
173             try {
174                 AAIResourceUri uri = AAIUriFactory.createResourceUri(AAIObjectType.IMAGE, cloudOwner, cloudRegionId, aaiImage.getImageId());
175                 if (!resourcesClient.exists(uri)) {
176                     transaction.create(uri, aaiImage);
177                     logger.debug("Queuing AAI command to add image: " + aaiImage.getImageId());
178                 } else {
179                     logger.debug("Nothing to add since image: " + aaiImage.getImageId() + "already exists in AAI.");
180                 }
181             } catch (WebApplicationException e) {
182                 throw new HeatBridgeException("Failed to update image to AAI: " + aaiImage.getImageId() + ". Error"
183                     + " cause: " + e, e);
184             }
185         }
186     }
187
188     @Override
189     public void buildAddFlavorsToAaiAction(final List<org.openstack4j.model.compute.Flavor> flavors)
190         throws HeatBridgeException {
191         for (org.openstack4j.model.compute.Flavor flavor : flavors) {
192             Flavor aaiFlavor = aaiHelper.buildFlavor(flavor);
193             try {
194                 AAIResourceUri uri = AAIUriFactory.createResourceUri(AAIObjectType.FLAVOR, cloudOwner, cloudRegionId, aaiFlavor.getFlavorId());
195                 if (!resourcesClient.exists(uri)) {
196                     transaction.create(uri, aaiFlavor);
197                     logger.debug("Queuing AAI command to add flavor: " + aaiFlavor.getFlavorId());
198                 } else {
199                     logger.debug("Nothing to add since flavor: " + aaiFlavor.getFlavorId() + "already exists in AAI.");
200                 }
201             } catch (WebApplicationException e) {
202                 throw new HeatBridgeException("Failed to update flavor to AAI: " + aaiFlavor.getFlavorId() + ". Error"
203                     + " cause: " + e, e);
204             }
205         }
206     }
207
208     @Override
209     public void buildAddVserversToAaiAction(final String genericVnfId, final String vfModuleId,
210         final List<Server> servers) {
211         servers.forEach(server -> {
212             Vserver vserver = aaiHelper.buildVserver(server.getId(), server);
213
214             // Build vserver relationships to: image, flavor, pserver, vf-module
215             vserver.setRelationshipList(aaiHelper.getVserverRelationshipList(cloudOwner, cloudRegionId, genericVnfId,
216                 vfModuleId, server));
217             transaction.create(AAIUriFactory.createResourceUri(AAIObjectType.VSERVER, cloudOwner, cloudRegionId, tenantId, vserver.getVserverId()), vserver);
218         });
219     }
220
221     @Override
222     public void buildAddVserverLInterfacesToAaiAction(final List<Resource> stackResources,
223         final List<String> oobMgtNetIds) {
224         Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
225         List<String> portIds = extractStackResourceIdsByResourceType(stackResources,
226             HeatBridgeConstants.OS_PORT_RESOURCE_TYPE);
227         for (String portId : portIds) {
228             Port port = osClient.getPortById(portId);
229             LInterface lIf = new LInterface();
230             lIf.setInterfaceId(port.getId());
231             lIf.setInterfaceName(port.getName());
232             lIf.setMacaddr(port.getMacAddress());
233             if (oobMgtNetIds != null && oobMgtNetIds.contains(port.getNetworkId())) {
234                 lIf.setInterfaceRole(OOB_MGT_NETWORK_IDENTIFIER);
235             } else {
236                 lIf.setInterfaceRole(port.getvNicType());
237             }
238
239             updateLInterfaceIps(port, lIf);
240             updateLInterfaceVlan(port, lIf);
241
242             // Update l-interface to the vserver
243             transaction.create(AAIUriFactory.createResourceUri(
244                 AAIObjectType.L_INTERFACE, cloudOwner, cloudRegionId, tenantId, port.getDeviceId(), lIf.getInterfaceName()), lIf);
245         }
246     }
247
248     private void updateLInterfaceVlan(final Port port, final LInterface lIf) {
249         Vlan vlan = new Vlan();
250         Network network = osClient.getNetworkById(port.getNetworkId());
251         lIf.setNetworkName(network.getName());
252         if (network.getNetworkType().equals(NetworkType.VLAN)) {
253             vlan.setVlanInterface(network.getProviderSegID());
254             Vlans vlans = new Vlans();
255             List<Vlan> vlanList = vlans.getVlan();
256             vlanList.add(vlan);
257             lIf.setVlans(vlans);
258         }
259         // Build sriov-vf to the l-interface
260         if (port.getvNicType().equalsIgnoreCase(HeatBridgeConstants.OS_SRIOV_PORT_TYPE)) {
261             SriovVfs sriovVfs = new SriovVfs();
262             // JAXB does not generate setters for list, however getter ensures its creation.
263             // Thus, all list manipulations must be made on live list.
264             List<SriovVf> sriovVfList = sriovVfs.getSriovVf();
265             SriovVf sriovVf = new SriovVf();
266             sriovVf.setPciId(port.getProfile().get(HeatBridgeConstants.OS_PCI_SLOT_KEY).toString());
267             sriovVf.setNeutronNetworkId(port.getNetworkId());
268             if (port.getVifDetails() != null) {
269                 sriovVf.setVfVlanFilter((String) port.getVifDetails().get(HeatBridgeConstants.OS_VLAN_NETWORK_KEY));
270             }
271             sriovVfList.add(sriovVf);
272
273             lIf.setSriovVfs(sriovVfs);
274
275             // For the given port create sriov-pf for host pserver/p-interface if absent
276             updateSriovPfToPserver(port, lIf);
277         }
278     }
279
280     /**
281      * Needs to be corrected according to the specification that is in draft
282      * If pserver/p-interface does not have a SRIOV-PF object matching the PCI-ID of the Openstack port object, then
283      * create it in AAI.
284      * Openstack SRIOV Port object has pci-id (to match sriov-pf on pserver/p-interface), physical-network ID (that
285      * matches the p-interface name).
286      *
287      * @param port Openstack port object
288      * @param lIf AAI l-interface object
289      */
290     private void updateSriovPfToPserver(final Port port, final LInterface lIf) {
291         if (port.getProfile() == null || Strings
292             .isNullOrEmpty(port.getProfile().get(HeatBridgeConstants.OS_PHYSICAL_NETWORK_KEY).toString())) {
293             logger.debug("The SRIOV port:" + port.getName() + " is missing physical-network-id, cannot update "
294                 + "sriov-pf object for host pserver: " + port.getHostId());
295             return;
296         }
297         Optional<String> matchingPifName = HeatBridgeUtils
298             .getMatchingPserverPifName(port.getProfile().get(HeatBridgeConstants.OS_PHYSICAL_NETWORK_KEY).toString());
299         if (matchingPifName.isPresent()) {
300             // Update l-interface description
301             String pserverHostName = port.getHostId();
302             lIf.setInterfaceDescription(
303                 "Attached to SR-IOV port: " + pserverHostName + "::" + matchingPifName.get());
304             try {
305                 Optional<PInterface> matchingPIf = resourcesClient.get(PInterface.class, 
306                         AAIUriFactory.createResourceUri(AAIObjectType.P_INTERFACE, pserverHostName, matchingPifName.get()).depth(Depth.ONE));
307                 if (matchingPIf.isPresent()) {
308                 SriovPfs pIfSriovPfs = matchingPIf.get().getSriovPfs();
309                     if (pIfSriovPfs == null) {
310                         pIfSriovPfs = new SriovPfs();
311                     }
312                     // Extract PCI-ID from OS port object
313                     String pfPciId = port.getProfile().get(HeatBridgeConstants.OS_PCI_SLOT_KEY).toString();
314     
315                     List<SriovPf> existingSriovPfs = pIfSriovPfs.getSriovPf();
316                     if (CollectionUtils.isEmpty(existingSriovPfs) || existingSriovPfs.stream()
317                         .noneMatch(existingSriovPf -> existingSriovPf.getPfPciId().equals(pfPciId))) {
318                         // Add sriov-pf object with PCI-ID to AAI
319                         SriovPf sriovPf = new SriovPf();
320                         sriovPf.setPfPciId(pfPciId);
321                         logger.debug("Queuing AAI command to update sriov-pf object to pserver: " + pserverHostName + "/" +
322                             matchingPifName.get());
323                         transaction.create(AAIUriFactory.createResourceUri(
324                                 AAIObjectType.SRIOV_PF, pserverHostName, matchingPifName.get(), sriovPf.getPfPciId()), sriovPf);
325                     }
326                 }
327             } catch (WebApplicationException e) {
328                 // Silently log that we failed to update the Pserver p-interface with PCI-ID
329                 logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.GENERAL_EXCEPTION, pserverHostName, matchingPifName.get(), cloudOwner,
330                     tenantId, "OpenStack", "Heatbridge", ErrorCode.DataError.getValue(), "Exception - Failed to add sriov-pf object to pserver", e);
331             }
332         }
333     }
334
335     private void updateLInterfaceIps(final Port port, final LInterface lIf) {
336         List<L3InterfaceIpv4AddressList> lInterfaceIps = lIf.getL3InterfaceIpv4AddressList();
337         for (IP ip : port.getFixedIps()) {
338             String ipAddress = ip.getIpAddress();
339             if (InetAddressValidator.getInstance().isValidInet4Address(ipAddress)) {
340                 L3InterfaceIpv4AddressList lInterfaceIp = new L3InterfaceIpv4AddressList();
341                 lInterfaceIp.setL3InterfaceIpv4Address(ipAddress);
342                 lInterfaceIp.setNeutronNetworkId(port.getNetworkId());
343                 lInterfaceIp.setNeutronSubnetId(ip.getSubnetId());
344                 lInterfaceIp.setL3InterfaceIpv4PrefixLength(32L);
345                 lInterfaceIps.add(lInterfaceIp);
346             }
347         }
348     }
349
350     @Override
351     public void submitToAai() throws HeatBridgeException {
352         try {
353             transaction.execute();
354         } catch (BulkProcessFailed e) {
355             String msg = "Failed to commit transaction";
356             logger.debug(msg + " with error: " + e);
357             throw new HeatBridgeException(msg, e);
358         }
359     }
360
361     private <T> Predicate<T> distinctByProperty(Function<? super T, Object> keyExtractor) {
362         Map<Object, Boolean> map = new ConcurrentHashMap<>();
363         return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
364     }
365 }