summaryrefslogtreecommitdiffstats
path: root/adapters/mso-openstack-adapters/src
diff options
context:
space:
mode:
authorSteve Smokowski <ss835w@att.com>2019-03-25 12:09:39 +0000
committerGerrit Code Review <gerrit@onap.org>2019-03-25 12:09:39 +0000
commit8e1de081db6c6c87d05687125c0316877ae1bec3 (patch)
treecb4f28f79eaf2437e74e0b904dcebe22614bb6b3 /adapters/mso-openstack-adapters/src
parent0d97f87b6381ea06350933b375c4e821b5e10568 (diff)
parent563a3579e907daa3952e10e25071b676430fd09d (diff)
Merge "heatbridge implementation for openstack-adapter"
Diffstat (limited to 'adapters/mso-openstack-adapters/src')
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java94
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeApi.java128
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeException.java29
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeImpl.java365
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/constants/HeatBridgeConstants.java76
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactory.java41
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactoryImpl.java77
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/helpers/AaiHelper.java275
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackAccess.java120
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClient.java69
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientException.java29
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientImpl.java69
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV2ClientImpl.java37
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV3ClientImpl.java37
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactory.java27
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactoryImpl.java74
-rw-r--r--adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/utils/HeatBridgeUtils.java61
-rw-r--r--adapters/mso-openstack-adapters/src/test/java/org/onap/so/heatbridge/HeatBridgeImplTest.java375
-rw-r--r--adapters/mso-openstack-adapters/src/test/resources/stack-resources.json441
19 files changed, 2414 insertions, 10 deletions
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java
index 3913d7f758..949027f7c1 100644
--- a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java
@@ -37,10 +37,22 @@ import java.util.concurrent.TimeUnit;
import javax.jws.WebService;
import javax.xml.ws.Holder;
+import org.apache.commons.collections.CollectionUtils;
+import org.onap.so.adapters.valet.GenericValetResponse;
+import org.onap.so.adapters.valet.ValetClient;
+import org.onap.so.adapters.valet.beans.HeatRequest;
+import org.onap.so.adapters.valet.beans.ValetConfirmResponse;
+import org.onap.so.adapters.valet.beans.ValetCreateResponse;
+import org.onap.so.adapters.valet.beans.ValetDeleteResponse;
+import org.onap.so.adapters.valet.beans.ValetRollbackResponse;
+import org.onap.so.adapters.valet.beans.ValetStatus;
+import org.onap.so.adapters.valet.beans.ValetUpdateResponse;
import org.onap.so.adapters.vnf.exceptions.VnfAlreadyExists;
import org.onap.so.adapters.vnf.exceptions.VnfException;
import org.onap.so.adapters.vnf.exceptions.VnfNotFound;
+import org.onap.so.client.aai.AAIResourcesClient;
import org.onap.so.cloud.CloudConfig;
+import org.onap.so.db.catalog.beans.CloudIdentity;
import org.onap.so.db.catalog.beans.CloudSite;
import org.onap.so.db.catalog.beans.HeatEnvironment;
import org.onap.so.db.catalog.beans.HeatFiles;
@@ -54,27 +66,26 @@ import org.onap.so.db.catalog.data.repository.VnfResourceRepository;
import org.onap.so.db.catalog.utils.MavenLikeVersioning;
import org.onap.so.entity.MsoRequest;
import org.onap.so.logger.ErrorCode;
+import org.onap.so.heatbridge.HeatBridgeApi;
+import org.onap.so.heatbridge.HeatBridgeImpl;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
import org.onap.so.logger.MessageEnum;
import org.onap.so.openstack.beans.HeatStatus;
import org.onap.so.openstack.beans.StackInfo;
import org.onap.so.openstack.beans.VnfRollback;
import org.onap.so.openstack.beans.VnfStatus;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
import org.onap.so.openstack.exceptions.MsoException;
import org.onap.so.openstack.exceptions.MsoExceptionCategory;
import org.onap.so.openstack.exceptions.MsoHeatNotFoundException;
import org.onap.so.openstack.utils.MsoHeatEnvironmentEntry;
import org.onap.so.openstack.utils.MsoHeatUtils;
import org.onap.so.openstack.utils.MsoHeatUtilsWithUpdate;
-import org.onap.so.adapters.valet.ValetClient;
-import org.onap.so.adapters.valet.beans.HeatRequest;
-import org.onap.so.adapters.valet.beans.ValetConfirmResponse;
-import org.onap.so.adapters.valet.beans.ValetCreateResponse;
-import org.onap.so.adapters.valet.beans.ValetDeleteResponse;
-import org.onap.so.adapters.valet.beans.ValetRollbackResponse;
-import org.onap.so.adapters.valet.beans.ValetStatus;
-import org.onap.so.adapters.valet.beans.ValetUpdateResponse;
-import org.onap.so.adapters.valet.GenericValetResponse;
+import org.openstack4j.model.compute.Flavor;
+import org.openstack4j.model.compute.Image;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.heat.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -509,6 +520,67 @@ public class MsoVnfAdapterImpl implements MsoVnfAdapter {
}
}
+ private void heatbridge(StackInfo heatStack, String cloudSiteId, String tenantId, String genericVnfName,
+ String vfModuleId) {
+ try {
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ CloudIdentity cloudIdentity = cloudSite.getIdentityService();
+ String heatStackId = heatStack.getCanonicalName().split("/")[1];
+
+ String cloudOwner = "CloudOwner";//cloud owner needs to come from bpmn-adapter
+ List<String> oobMgtNetNames = new ArrayList<>();
+
+ HeatBridgeApi heatBridgeClient = new HeatBridgeImpl(new AAIResourcesClient(), cloudIdentity,
+ cloudOwner, cloudSiteId, tenantId);
+
+ OpenstackClient openstackClient = heatBridgeClient.authenticate();
+ List<Resource> stackResources = heatBridgeClient.queryNestedHeatStackResources(heatStackId);
+
+ List<Server> osServers = heatBridgeClient.getAllOpenstackServers(stackResources);
+
+ List<Image> osImages = heatBridgeClient.extractOpenstackImagesFromServers(osServers);
+
+ List<Flavor> osFlavors = heatBridgeClient.extractOpenstackFlavorsFromServers(osServers);
+
+ logger.debug("Successfully queried heat stack{} for resources.", heatStackId);
+ //os images
+ if (osImages != null && !osImages.isEmpty()) {
+ heatBridgeClient.buildAddImagesToAaiAction(osImages);
+ logger.debug("Successfully built AAI actions to add images.");
+ } else {
+ logger.debug("No images to update to AAI.");
+ }
+ //flavors
+ if (osFlavors != null && !osFlavors.isEmpty()) {
+ heatBridgeClient.buildAddFlavorsToAaiAction(osFlavors);
+ logger.debug("Successfully built AAI actions to add flavors.");
+ } else {
+ logger.debug("No flavors to update to AAI.");
+ }
+
+ //compute resources
+ heatBridgeClient.buildAddVserversToAaiAction(genericVnfName, vfModuleId, osServers);
+ logger.debug("Successfully queried compute resources and built AAI vserver actions.");
+
+ //neutron resources
+ List<String> oobMgtNetIds = new ArrayList<>();
+
+ //if no network-id list is provided, however network-name list is
+ if (!CollectionUtils.isEmpty(oobMgtNetNames)) {
+ oobMgtNetIds = heatBridgeClient.extractNetworkIds(oobMgtNetNames);
+ }
+ heatBridgeClient.buildAddVserverLInterfacesToAaiAction(stackResources, oobMgtNetIds);
+ logger.debug(
+ "Successfully queried neutron resources and built AAI actions to add l-interfaces to vservers.");
+
+ //Update AAI
+ heatBridgeClient.submitToAai();
+ } catch (Exception ex) {
+ logger.debug("Heatbrige failed for stackId: " + heatStack.getCanonicalName(), ex);
+ }
+ }
+
private String convertNode(final JsonNode node) {
try {
final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
@@ -1271,7 +1343,9 @@ public class MsoVnfAdapterImpl implements MsoVnfAdapter {
logger.error("Exception encountered while sending Confirm to Valet ", e);
}
}
- logger.debug("VF Module {} successfully created", vfModuleName);
+ logger.debug ("VF Module {} successfully created", vfModuleName);
+ //call heatbridge
+ heatbridge(heatStack, cloudSiteId, tenantId, genericVnfName, vfModuleId);
return;
} catch (Exception e) {
logger.debug("unhandled exception in create VF",e);
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeApi.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeApi.java
new file mode 100644
index 0000000000..6b06761474
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeApi.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge;
+
+import java.util.List;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.openstack4j.model.compute.Flavor;
+import org.openstack4j.model.compute.Image;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.heat.Resource;
+
+/**
+ * Defines the contract to extract Heat Stack Resources from Openstack and inventory it to AAI.
+ * This API is used only to "create" objects in AAI.
+ */
+public interface HeatBridgeApi {
+
+ /**
+ * Authenticate with Openstack Keystone. The auth information is read from SO cloud configuration file.
+ *
+ * @return Openstack client object with keystone token
+ * @throws HeatBridgeException upon failure to authenticate with keystone
+ */
+ OpenstackClient authenticate() throws HeatBridgeException;
+
+ /**
+ * Query all the stack based resources from Openstack Heat service
+ *
+ * @param heatStackId Heat stack UUID
+ * @return A list of stack based resources
+ */
+ List<Resource> queryNestedHeatStackResources(String heatStackId);
+
+ /**
+ * Get a filtered list of resource IDs by resource type
+ *
+ * @param stackResources A list of stack based resources
+ * @param resourceType Resource type to filter by
+ * @return A list of stack resources matching the specified resource-type
+ */
+ List<String> extractStackResourceIdsByResourceType(List<Resource> stackResources, String resourceType);
+
+ /**
+ * Get network IDs for a given list of network names.
+ * It is assumed that there is a one to one mapping between the name and ID.
+ * @param networkNameList List of network names
+ * @return List of matching network IDs
+ */
+ List<String> extractNetworkIds(List<String> networkNameList);
+
+ /**
+ * Query the Openstack server objects from the list of stack resources
+ *
+ * @param stackResources A list of stack based resources
+ * @return A list of Openstack Server objects
+ */
+ List<Server> getAllOpenstackServers(List<Resource> stackResources);
+
+ /**
+ * Extract Openstack Image objects from a a list of Server objects
+ *
+ * @param servers A list of Openstack Server objects
+ * @return A list of Openstack Image objects
+ */
+ List<Image> extractOpenstackImagesFromServers(List<Server> servers);
+
+ /**
+ * Extract Openstack Flavor objects from a a list of Server objects
+ *
+ * @param servers A list of Openstack Server objects
+ * @return A list of Openstack Flavor objects
+ */
+ List<Flavor> extractOpenstackFlavorsFromServers(List<Server> servers);
+
+ /**
+ * Query and build AAI actions for Openstack Image resources to AAI's image objects
+ *
+ * @param images List of Openstack Image objects
+ * @throws HeatBridgeException when failing to add images to AAI
+ */
+ void buildAddImagesToAaiAction(List<Image> images) throws HeatBridgeException;
+
+ /**
+ * Query and build AAI actions for Openstack Flavor resources to AAI's flavor objects
+ *
+ * @param flavors List of Openstack Flavor objects
+ * @throws HeatBridgeException when failing to add flavors to AAI
+ */
+ void buildAddFlavorsToAaiAction(List<Flavor> flavors) throws HeatBridgeException;
+
+ /**
+ * Query and build AAI actions for Openstack Compute resources to AAI's vserver objects
+ *
+ * @param genericVnfId AAI generic-vnf-id
+ * @param vfModuleId AAI vf-module-id
+ * @param servers Openstack Server list
+ */
+ void buildAddVserversToAaiAction(String genericVnfId, String vfModuleId, List<Server> servers);
+
+ /**
+ * Query and build AAI actions for Openstack Neutron resources associated with a Compute resource to AAI's
+ * l-interface objects
+ *
+ * @param stackResources Openstack Heat stack resource list
+ * @param oobMgtNetIds List of OOB network IDs list
+ */
+ void buildAddVserverLInterfacesToAaiAction(List<Resource> stackResources, List<String> oobMgtNetIds);
+
+ /**
+ * Execute AAI restful API to update the Openstack resources
+ *
+ * @throws HeatBridgeException when failing to add openstack resource PoJos to AAI
+ */
+ void submitToAai() throws HeatBridgeException;
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeException.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeException.java
new file mode 100644
index 0000000000..f993d71e4c
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge;
+
+public class HeatBridgeException extends Exception {
+
+ private static final long serialVersionUID = -1472047930391718894L;
+
+ public HeatBridgeException(final String message) {
+ super(message);
+ }
+
+ public HeatBridgeException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeImpl.java
new file mode 100644
index 0000000000..90ceeb7d1c
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeImpl.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.ws.rs.WebApplicationException;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.validator.routines.InetAddressValidator;
+import org.onap.aai.domain.yang.Flavor;
+import org.onap.aai.domain.yang.Image;
+import org.onap.aai.domain.yang.L3InterfaceIpv4AddressList;
+import org.onap.aai.domain.yang.LInterface;
+import org.onap.aai.domain.yang.PInterface;
+import org.onap.aai.domain.yang.SriovPf;
+import org.onap.aai.domain.yang.SriovPfs;
+import org.onap.aai.domain.yang.SriovVf;
+import org.onap.aai.domain.yang.SriovVfs;
+import org.onap.aai.domain.yang.Vlan;
+import org.onap.aai.domain.yang.Vlans;
+import org.onap.aai.domain.yang.Vserver;
+import org.onap.so.client.aai.AAIObjectType;
+import org.onap.so.client.aai.AAIResourcesClient;
+import org.onap.so.client.aai.AAISingleTransactionClient;
+import org.onap.so.client.aai.entities.uri.AAIResourceUri;
+import org.onap.so.client.aai.entities.uri.AAIUriFactory;
+import org.onap.so.client.graphinventory.entities.uri.Depth;
+import org.onap.so.client.graphinventory.exceptions.BulkProcessFailed;
+import org.onap.so.db.catalog.beans.CloudIdentity;
+import org.onap.so.heatbridge.constants.HeatBridgeConstants;
+import org.onap.so.heatbridge.factory.MsoCloudClientFactoryImpl;
+import org.onap.so.heatbridge.helpers.AaiHelper;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.onap.so.heatbridge.openstack.factory.OpenstackClientFactoryImpl;
+import org.onap.so.heatbridge.utils.HeatBridgeUtils;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.heat.Resource;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * This class provides an implementation of {@link HeatBridgeApi}
+ */
+public class HeatBridgeImpl implements HeatBridgeApi {
+
+ private static final Logger logger = LoggerFactory.getLogger(HeatBridgeImpl.class);
+ private static final String ERR_MSG_NULL_OS_CLIENT = "Initialization error: Null openstack client. Authenticate with Keystone first.";
+ private static final String OOB_MGT_NETWORK_IDENTIFIER = "Management";
+ private OpenstackClient osClient;
+ private AAIResourcesClient resourcesClient;
+ private AAISingleTransactionClient transaction;
+ private String cloudOwner;
+ private String cloudRegionId;
+ private String tenantId;
+ private AaiHelper aaiHelper = new AaiHelper();
+ private CloudIdentity cloudIdentity;
+
+
+ public HeatBridgeImpl(AAIResourcesClient resourcesClient, final CloudIdentity cloudIdentity,
+ @Nonnull final String cloudOwner, @Nonnull final String cloudRegionId, @Nonnull final String tenantId) {
+ Objects.requireNonNull(cloudOwner, "Null cloud-owner value!");
+ Objects.requireNonNull(cloudRegionId, "Null cloud-region identifier!");
+ Objects.requireNonNull(tenantId, "Null tenant identifier!");
+ Objects.requireNonNull(tenantId, "Null AAI actions list!");
+
+ this.cloudIdentity = cloudIdentity;
+ this.cloudOwner = cloudOwner;
+ this.cloudRegionId = cloudRegionId;
+ this.tenantId = tenantId;
+ this.resourcesClient = resourcesClient;
+ this.transaction = resourcesClient.beginSingleTransaction();
+ }
+
+ @Override
+ public OpenstackClient authenticate() throws HeatBridgeException {
+ this.osClient = new MsoCloudClientFactoryImpl(new OpenstackClientFactoryImpl())
+ .getOpenstackClient(cloudIdentity.getIdentityUrl(), cloudIdentity.getMsoId(), cloudIdentity.getMsoPass(), cloudRegionId, tenantId);
+ logger.debug("Successfully authenticated with keystone for tenant: " + tenantId + " and cloud "
+ + "region: " + cloudRegionId);
+ return osClient;
+ }
+
+ @Override
+ public List<Resource> queryNestedHeatStackResources(final String heatStackId) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+ Preconditions.checkState(!Strings.isNullOrEmpty(heatStackId), "Invalid heatStackId!");
+ List<Resource> stackBasedResources = osClient
+ .getStackBasedResources(heatStackId, HeatBridgeConstants.OS_DEFAULT_HEAT_NESTING);
+ logger.debug(stackBasedResources.size() + " heat stack resources are extracted for stack: " + heatStackId);
+ return stackBasedResources;
+ }
+
+ @Override
+ public List<String> extractStackResourceIdsByResourceType(final List<Resource> stackResources,
+ final String resourceType) {
+ return stackResources.stream()
+ .filter(stackResource -> stackResource.getType().equals(resourceType))
+ .map(Resource::getPhysicalResourceId)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> extractNetworkIds(final List<String> networkNameList) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+ return networkNameList.stream()
+ .map(netName -> osClient.listNetworksByFilter(ImmutableMap.of(HeatBridgeConstants.OS_NAME_KEY, netName)))
+ .filter(nets -> nets != null && nets.size() == 1) //extract network-id only if network-name is unique
+ .map(nets -> nets.get(0).getId())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Server> getAllOpenstackServers(final List<Resource> stackResources) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+
+ // Filter Openstack Compute resources
+ List<String> serverIds = extractStackResourceIdsByResourceType(stackResources,
+ HeatBridgeConstants.OS_SERVER_RESOURCE_TYPE);
+ return serverIds.stream().map(serverId -> osClient.getServerById(serverId)).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<org.openstack4j.model.compute.Image> extractOpenstackImagesFromServers(final List<Server> servers) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+ return servers.stream().map(Server::getImage)
+ .filter(distinctByProperty(org.openstack4j.model.compute.Image::getId)).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<org.openstack4j.model.compute.Flavor> extractOpenstackFlavorsFromServers(final List<Server> servers) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+ return servers.stream().map(Server::getFlavor)
+ .filter(distinctByProperty(org.openstack4j.model.compute.Flavor::getId)).collect(Collectors.toList());
+ }
+
+ @Override
+ public void buildAddImagesToAaiAction(final List<org.openstack4j.model.compute.Image> images)
+ throws HeatBridgeException {
+ for (org.openstack4j.model.compute.Image image : images) {
+ Image aaiImage = aaiHelper.buildImage(image);
+ try {
+ AAIResourceUri uri = AAIUriFactory.createResourceUri(AAIObjectType.IMAGE, cloudOwner, cloudRegionId, aaiImage.getImageId());
+ if (!resourcesClient.exists(uri)) {
+ transaction.create(uri, aaiImage);
+ logger.debug("Queuing AAI command to add image: " + aaiImage.getImageId());
+ } else {
+ logger.debug("Nothing to add since image: " + aaiImage.getImageId() + "already exists in AAI.");
+ }
+ } catch (WebApplicationException e) {
+ throw new HeatBridgeException("Failed to update image to AAI: " + aaiImage.getImageId() + ". Error"
+ + " cause: " + e, e);
+ }
+ }
+ }
+
+ @Override
+ public void buildAddFlavorsToAaiAction(final List<org.openstack4j.model.compute.Flavor> flavors)
+ throws HeatBridgeException {
+ for (org.openstack4j.model.compute.Flavor flavor : flavors) {
+ Flavor aaiFlavor = aaiHelper.buildFlavor(flavor);
+ try {
+ AAIResourceUri uri = AAIUriFactory.createResourceUri(AAIObjectType.FLAVOR, cloudOwner, cloudRegionId, aaiFlavor.getFlavorId());
+ if (!resourcesClient.exists(uri)) {
+ transaction.create(uri, aaiFlavor);
+ logger.debug("Queuing AAI command to add flavor: " + aaiFlavor.getFlavorId());
+ } else {
+ logger.debug("Nothing to add since flavor: " + aaiFlavor.getFlavorId() + "already exists in AAI.");
+ }
+ } catch (WebApplicationException e) {
+ throw new HeatBridgeException("Failed to update flavor to AAI: " + aaiFlavor.getFlavorId() + ". Error"
+ + " cause: " + e, e);
+ }
+ }
+ }
+
+ @Override
+ public void buildAddVserversToAaiAction(final String genericVnfId, final String vfModuleId,
+ final List<Server> servers) {
+ servers.forEach(server -> {
+ Vserver vserver = aaiHelper.buildVserver(server.getId(), server);
+
+ // Build vserver relationships to: image, flavor, pserver, vf-module
+ vserver.setRelationshipList(aaiHelper.getVserverRelationshipList(cloudOwner, cloudRegionId, genericVnfId,
+ vfModuleId, server));
+ transaction.create(AAIUriFactory.createResourceUri(AAIObjectType.VSERVER, cloudOwner, cloudRegionId, tenantId, vserver.getVserverId()), vserver);
+ });
+ }
+
+ @Override
+ public void buildAddVserverLInterfacesToAaiAction(final List<Resource> stackResources,
+ final List<String> oobMgtNetIds) {
+ Objects.requireNonNull(osClient, ERR_MSG_NULL_OS_CLIENT);
+ List<String> portIds = extractStackResourceIdsByResourceType(stackResources,
+ HeatBridgeConstants.OS_PORT_RESOURCE_TYPE);
+ for (String portId : portIds) {
+ Port port = osClient.getPortById(portId);
+ LInterface lIf = new LInterface();
+ lIf.setInterfaceId(port.getId());
+ lIf.setInterfaceName(port.getName());
+ lIf.setMacaddr(port.getMacAddress());
+ if (oobMgtNetIds != null && oobMgtNetIds.contains(port.getNetworkId())) {
+ lIf.setInterfaceRole(OOB_MGT_NETWORK_IDENTIFIER);
+ } else {
+ lIf.setInterfaceRole(port.getvNicType());
+ }
+
+ updateLInterfaceIps(port, lIf);
+ updateLInterfaceVlan(port, lIf);
+
+ // Update l-interface to the vserver
+ transaction.create(AAIUriFactory.createResourceUri(
+ AAIObjectType.L_INTERFACE, cloudOwner, cloudRegionId, tenantId, port.getDeviceId(), lIf.getInterfaceName()), lIf);
+ }
+ }
+
+ private void updateLInterfaceVlan(final Port port, final LInterface lIf) {
+ Vlan vlan = new Vlan();
+ Network network = osClient.getNetworkById(port.getNetworkId());
+ lIf.setNetworkName(network.getName());
+ if (network.getNetworkType().equals(NetworkType.VLAN)) {
+ vlan.setVlanInterface(network.getProviderSegID());
+ Vlans vlans = new Vlans();
+ List<Vlan> vlanList = vlans.getVlan();
+ vlanList.add(vlan);
+ lIf.setVlans(vlans);
+ }
+ // Build sriov-vf to the l-interface
+ if (port.getvNicType().equalsIgnoreCase(HeatBridgeConstants.OS_SRIOV_PORT_TYPE)) {
+ SriovVfs sriovVfs = new SriovVfs();
+ // JAXB does not generate setters for list, however getter ensures its creation.
+ // Thus, all list manipulations must be made on live list.
+ List<SriovVf> sriovVfList = sriovVfs.getSriovVf();
+ SriovVf sriovVf = new SriovVf();
+ sriovVf.setPciId(port.getProfile().get(HeatBridgeConstants.OS_PCI_SLOT_KEY).toString());
+ sriovVf.setNeutronNetworkId(port.getNetworkId());
+ if (port.getVifDetails() != null) {
+ sriovVf.setVfVlanFilter((String) port.getVifDetails().get(HeatBridgeConstants.OS_VLAN_NETWORK_KEY));
+ }
+ sriovVfList.add(sriovVf);
+
+ lIf.setSriovVfs(sriovVfs);
+
+ // For the given port create sriov-pf for host pserver/p-interface if absent
+ updateSriovPfToPserver(port, lIf);
+ }
+ }
+
+ /**
+ * Needs to be corrected according to the specification that is in draft
+ * If pserver/p-interface does not have a SRIOV-PF object matching the PCI-ID of the Openstack port object, then
+ * create it in AAI.
+ * Openstack SRIOV Port object has pci-id (to match sriov-pf on pserver/p-interface), physical-network ID (that
+ * matches the p-interface name).
+ *
+ * @param port Openstack port object
+ * @param lIf AAI l-interface object
+ */
+ private void updateSriovPfToPserver(final Port port, final LInterface lIf) {
+ if (port.getProfile() == null || Strings
+ .isNullOrEmpty(port.getProfile().get(HeatBridgeConstants.OS_PHYSICAL_NETWORK_KEY).toString())) {
+ logger.debug("The SRIOV port:" + port.getName() + " is missing physical-network-id, cannot update "
+ + "sriov-pf object for host pserver: " + port.getHostId());
+ return;
+ }
+ Optional<String> matchingPifName = HeatBridgeUtils
+ .getMatchingPserverPifName(port.getProfile().get(HeatBridgeConstants.OS_PHYSICAL_NETWORK_KEY).toString());
+ if (matchingPifName.isPresent()) {
+ // Update l-interface description
+ String pserverHostName = port.getHostId();
+ lIf.setInterfaceDescription(
+ "Attached to SR-IOV port: " + pserverHostName + "::" + matchingPifName.get());
+ try {
+ Optional<PInterface> matchingPIf = resourcesClient.get(PInterface.class,
+ AAIUriFactory.createResourceUri(AAIObjectType.P_INTERFACE, pserverHostName, matchingPifName.get()).depth(Depth.ONE));
+ if (matchingPIf.isPresent()) {
+ SriovPfs pIfSriovPfs = matchingPIf.get().getSriovPfs();
+ if (pIfSriovPfs == null) {
+ pIfSriovPfs = new SriovPfs();
+ }
+ // Extract PCI-ID from OS port object
+ String pfPciId = port.getProfile().get(HeatBridgeConstants.OS_PCI_SLOT_KEY).toString();
+
+ List<SriovPf> existingSriovPfs = pIfSriovPfs.getSriovPf();
+ if (CollectionUtils.isEmpty(existingSriovPfs) || existingSriovPfs.stream()
+ .noneMatch(existingSriovPf -> existingSriovPf.getPfPciId().equals(pfPciId))) {
+ // Add sriov-pf object with PCI-ID to AAI
+ SriovPf sriovPf = new SriovPf();
+ sriovPf.setPfPciId(pfPciId);
+ logger.debug("Queuing AAI command to update sriov-pf object to pserver: " + pserverHostName + "/" +
+ matchingPifName.get());
+ transaction.create(AAIUriFactory.createResourceUri(
+ AAIObjectType.SRIOV_PF, pserverHostName, matchingPifName.get(), sriovPf.getPfPciId()), sriovPf);
+ }
+ }
+ } catch (WebApplicationException e) {
+ // Silently log that we failed to update the Pserver p-interface with PCI-ID
+ logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.GENERAL_EXCEPTION, pserverHostName, matchingPifName.get(), cloudOwner,
+ tenantId, "OpenStack", "Heatbridge", ErrorCode.DataError.getValue(), "Exception - Failed to add sriov-pf object to pserver", e);
+ }
+ }
+ }
+
+ private void updateLInterfaceIps(final Port port, final LInterface lIf) {
+ List<L3InterfaceIpv4AddressList> lInterfaceIps = lIf.getL3InterfaceIpv4AddressList();
+ for (IP ip : port.getFixedIps()) {
+ String ipAddress = ip.getIpAddress();
+ if (InetAddressValidator.getInstance().isValidInet4Address(ipAddress)) {
+ L3InterfaceIpv4AddressList lInterfaceIp = new L3InterfaceIpv4AddressList();
+ lInterfaceIp.setL3InterfaceIpv4Address(ipAddress);
+ lInterfaceIp.setNeutronNetworkId(port.getNetworkId());
+ lInterfaceIp.setNeutronSubnetId(ip.getSubnetId());
+ lInterfaceIp.setL3InterfaceIpv4PrefixLength(32L);
+ lInterfaceIps.add(lInterfaceIp);
+ }
+ }
+ }
+
+ @Override
+ public void submitToAai() throws HeatBridgeException {
+ try {
+ transaction.execute();
+ } catch (BulkProcessFailed e) {
+ String msg = "Failed to commit transaction";
+ logger.debug(msg + " with error: " + e);
+ throw new HeatBridgeException(msg, e);
+ }
+ }
+
+ private <T> Predicate<T> distinctByProperty(Function<? super T, Object> keyExtractor) {
+ Map<Object, Boolean> map = new ConcurrentHashMap<>();
+ return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/constants/HeatBridgeConstants.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/constants/HeatBridgeConstants.java
new file mode 100644
index 0000000000..1f302341ad
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/constants/HeatBridgeConstants.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.constants;
+
+public class HeatBridgeConstants {
+
+ private HeatBridgeConstants() {
+ throw new IllegalStateException("Trying to instantiate a constants class.");
+ }
+
+ /**
+ * Openstack related constants
+ */
+ public static final Integer OS_DEFAULT_HEAT_NESTING = 5;
+ public static final String OS_SERVER_RESOURCE_TYPE = "OS::Nova::Server";
+ public static final String OS_PORT_RESOURCE_TYPE = "OS::Neutron::Port";
+ public static final String OS_SRIOV_PORT_TYPE = "direct";
+ public static final String OS_PCI_SLOT_KEY = "pci_slot";
+ public static final String OS_PHYSICAL_NETWORK_KEY = "physical_network";
+ public static final String OS_VLAN_NETWORK_KEY = "vlan";
+ public static final String OS_UNKNOWN_KEY = "unknown";
+ public static final String OS_RESOURCES_SELF_LINK_KEY = "self";
+ public static final String OS_DEFAULT_DOMAIN_NAME = "default";
+ public static final String OS_KEYSTONE_V2_KEY = "v2.0";
+ public static final String OS_KEYSTONE_V3_KEY = "v3";
+ public static final String OS_NAME_KEY = "name";
+
+ /**
+ * AAI related constants
+ */
+ public static final String AAI_GENERIC_VNF = "generic-vnf";
+ public static final String AAI_GENERIC_VNF_ID = "generic-vnf.vnf-id";
+ public static final String AAI_PSERVER = "pserver";
+ public static final String AAI_VSERVER = "vserver";
+ public static final String AAI_PSERVER_HOSTNAME = "pserver.hostname";
+ public static final String AAI_VF_MODULE = "vf-module";
+ public static final String AAI_VF_MODULE_ID = "vf-module.vf-module-id";
+ public static final String AAI_IMAGE = "image";
+ public static final String AAI_IMAGE_ID = "image.image-id";
+ public static final String AAI_CLOUD_OWNER = "cloud-region.cloud-owner";
+ public static final String AAI_CLOUD_REGION_ID = "cloud-region.cloud-region-id";
+ public static final String AAI_FLAVOR = "flavor";
+ public static final String AAI_FLAVOR_ID = "flavor.flavor-id";
+ public static final String AAI_RESOURCE_DEPTH_ALL = "all";
+ public static final String AAI_SRIOV_PF = "sriov-pf";
+ public static final String AAI_P_INTERFACE_NAME = "p-interface.interface-name";
+ public static final String AAI_SRIOV_PF_PCI_ID = "sriov-pf.pf-pci-id";
+
+ /**
+ * Keys for internal usage
+ */
+ public static final String KEY_FLAVORS = "flavors";
+ public static final String KEY_IMAGES = "images";
+ public static final String KEY_VSERVERS = "vservers";
+ public static final String KEY_SRIOV_PFS = "pserverSriovPfs";
+ public static final String KEY_GLOBAL_SUBSCRIBER_ID = "globalSubscriberId";
+ public static final String KEY_SERVICE_TYPE = "subscriptionServiceType";
+ public static final String KEY_SERVICE_INSTANCE_ID = "serviceInstanceId";
+ public static final String KEY_VNF_INSTANCE_ID = "genericVnfId";
+ public static final String KEY_MSO_REQUEST_ID = "msoRequestId";
+ public static final String KEY_SO_WORKFLOW_EXCEPTION = "WorkflowException";
+ public static final String KEY_PROCESS_STATUS_MSG = "processStatusMsg";
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactory.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactory.java
new file mode 100644
index 0000000000..100b50e502
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.factory;
+
+import org.onap.so.heatbridge.HeatBridgeException;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+
+/**
+ * Defines contract to load the cloud configuration from SO, authenticate with keystone for a given cloud-region and
+ * tenant.
+ */
+public interface MsoCloudClientFactory {
+
+ /**
+ * Get the Openstack Client for keystone version specified in cloud configuration.
+ *
+ * @param url openstack url
+ * @param msoId openstack user for mso
+ * @param msoPass openstack password for mso user
+ * @param cloudRegionId cloud-region identifier
+ * @param tenantId tenant identifier
+ * @return Openstack Client for the keystone version requested
+ * @throws HeatBridgeException if any errors when reading cloud configuration or getting openstack client
+ */
+
+
+ OpenstackClient getOpenstackClient(String url, String msoId, String msoPass, String cloudRegionId, String tenantId) throws HeatBridgeException;
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactoryImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactoryImpl.java
new file mode 100644
index 0000000000..b70b32a4d6
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactoryImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.factory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+import org.onap.so.heatbridge.HeatBridgeException;
+import org.onap.so.heatbridge.constants.HeatBridgeConstants;
+import org.onap.so.heatbridge.openstack.api.OpenstackAccess;
+import org.onap.so.heatbridge.openstack.api.OpenstackAccess.OpenstackAccessBuilder;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.onap.so.heatbridge.openstack.api.OpenstackClientException;
+import org.onap.so.heatbridge.openstack.factory.OpenstackClientFactory;
+import org.onap.so.utils.CryptoUtils;
+
+/**
+ * This class implements {@link MsoCloudClientFactory}
+ * It loads the cloud configuration from SO and uses it to authenticate with keystone.
+ * As a result of authentication with keystone, it returns the Openstack client with the auth token so that
+ * subsequent API calls to Openstack can be made.
+ */
+public class MsoCloudClientFactoryImpl implements MsoCloudClientFactory {
+
+ private OpenstackClientFactory openstackClientFactory;
+
+ public MsoCloudClientFactoryImpl(@Nonnull OpenstackClientFactory openstackClientFactory) {
+ Objects.requireNonNull(openstackClientFactory, "Null OpenstackClientFactory object");
+ this.openstackClientFactory = openstackClientFactory;
+ }
+ @Override
+ public OpenstackClient getOpenstackClient(@Nonnull String url, @Nonnull String msoId, @Nonnull String msoPass, @Nonnull String cloudRegionId, @Nonnull String tenantId) throws
+ HeatBridgeException {
+ Objects.requireNonNull(url, "Null openstack url!");
+ Objects.requireNonNull(msoId, "Null openstack user id!");
+ Objects.requireNonNull(msoPass, "Null openstack password!");
+ Objects.requireNonNull(cloudRegionId, "Null cloud-region ID!");
+ Objects.requireNonNull(tenantId, "Null tenant ID!");
+ try {
+ final OpenstackAccess osAccess = new OpenstackAccessBuilder()
+ .setBaseUrl(url) // keystone URL
+ .setUser(msoId) // keystone username
+ .setPassword(CryptoUtils.decryptCloudConfigPassword(msoPass)) // keystone decrypted password
+ .setRegion(cloudRegionId) // openstack region
+ .setDomainName(HeatBridgeConstants.OS_DEFAULT_DOMAIN_NAME) // hardcode to "default"
+ .setTenantId(tenantId) // tenantId
+ .build();
+
+ // Identify the Keystone version
+ String version = new URL(url).getPath().replace("/", "");
+ if (version.equals(HeatBridgeConstants.OS_KEYSTONE_V2_KEY)) {
+ return openstackClientFactory.createOpenstackV2Client(osAccess);
+ } else if (version.equals(HeatBridgeConstants.OS_KEYSTONE_V3_KEY)) {
+ return openstackClientFactory.createOpenstackV3Client(osAccess);
+ }
+ throw new OpenstackClientException("Unsupported keystone version!");
+ } catch (MalformedURLException e) {
+ throw new HeatBridgeException("Malformed Keystone Endpoint in SO configuration.", e);
+ } catch (OpenstackClientException osClientEx) {
+ throw new HeatBridgeException("Client error when authenticating with the Openstack V3.", osClientEx);
+ }
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/helpers/AaiHelper.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/helpers/AaiHelper.java
new file mode 100644
index 0000000000..a0f1f0798f
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/helpers/AaiHelper.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.helpers;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
+import org.onap.aai.domain.yang.Flavor;
+import org.onap.aai.domain.yang.Image;
+import org.onap.aai.domain.yang.Relationship;
+import org.onap.aai.domain.yang.RelationshipData;
+import org.onap.aai.domain.yang.RelationshipList;
+import org.onap.aai.domain.yang.SriovVf;
+import org.onap.aai.domain.yang.Vserver;
+import org.onap.so.heatbridge.constants.HeatBridgeConstants;
+import org.openstack4j.model.compute.Server;
+
+/**
+ * This class provides wrapper methods to manage creation of AAI objects and extracting objects from AAI and
+ * transforming into required objects.
+ */
+public class AaiHelper {
+
+ /**
+ * Build vserver relationship object to entities: pserver, vf-module, image, flavor
+ *
+ * @param cloudOwner AAI cloudOwner value
+ * @param cloudRegionId AAI cloud-region identifier
+ * @param genericVnfId AAI generic-vnf identifier
+ * @param vfModuleId AAI vf-module identifier
+ * @param server Openstack Server object
+ */
+ public RelationshipList getVserverRelationshipList(final String cloudOwner, final String cloudRegionId, final String
+ genericVnfId, final String vfModuleId, final Server server) {
+ RelationshipList relationshipList = new RelationshipList();
+ List<Relationship> relationships = relationshipList.getRelationship();
+
+ // vserver to pserver relationship
+ Relationship pserverRelationship = buildRelationship(HeatBridgeConstants.AAI_PSERVER,
+ ImmutableMap.<String, String>builder()
+ .put(HeatBridgeConstants.AAI_PSERVER_HOSTNAME, server.getHypervisorHostname())
+ .build());
+ relationships.add(pserverRelationship);
+
+ // vserver to vf-module relationship
+ Relationship vfModuleRelationship = buildRelationship(HeatBridgeConstants.AAI_VF_MODULE,
+ ImmutableMap.<String, String>builder()
+ .put(HeatBridgeConstants.AAI_GENERIC_VNF_ID, genericVnfId)
+ .put(HeatBridgeConstants.AAI_VF_MODULE_ID, vfModuleId)
+ .build());
+ relationships.add(vfModuleRelationship);
+
+ // vserver to image relationship
+ Relationship imageRel = buildRelationship(HeatBridgeConstants.AAI_IMAGE,
+ ImmutableMap.<String, String>builder()
+ .put(HeatBridgeConstants.AAI_CLOUD_OWNER, cloudOwner)
+ .put(HeatBridgeConstants.AAI_CLOUD_REGION_ID, cloudRegionId)
+ .put(HeatBridgeConstants.AAI_IMAGE_ID, server.getImage().getId())
+ .build());
+ relationships.add(imageRel);
+
+ // vserver to flavor relationship
+ Relationship flavorRel = buildRelationship(HeatBridgeConstants.AAI_FLAVOR,
+ ImmutableMap.<String, String>builder()
+ .put(HeatBridgeConstants.AAI_CLOUD_OWNER, cloudOwner)
+ .put(HeatBridgeConstants.AAI_CLOUD_REGION_ID, cloudRegionId)
+ .put(HeatBridgeConstants.AAI_FLAVOR_ID, server.getFlavor().getId())
+ .build());
+ relationships.add(flavorRel);
+ return relationshipList;
+ }
+
+ public RelationshipList getLInterfaceRelationshipList(final String pserverName, final String pIfName,
+ final String pfPciId) {
+ RelationshipList relationshipList = new RelationshipList();
+ List<Relationship> relationships = relationshipList.getRelationship();
+
+ // sriov-vf to sriov-pf relationship
+ Relationship sriovPfRelationship = buildRelationship(HeatBridgeConstants.AAI_SRIOV_PF,
+ ImmutableMap.<String, String>builder()
+ .put(HeatBridgeConstants.AAI_PSERVER_HOSTNAME, pserverName)
+ .put(HeatBridgeConstants.AAI_P_INTERFACE_NAME, pIfName)
+ .put(HeatBridgeConstants.AAI_SRIOV_PF_PCI_ID, pfPciId)
+ .build());
+ relationships.add(sriovPfRelationship);
+
+ return relationshipList;
+ }
+
+ /**
+ * Transform Openstack Server object to AAI Vserver object
+ *
+ * @param serverId Openstack server identifier
+ * @param server Openstack server object
+ * @return AAI Vserver object
+ */
+ public Vserver buildVserver(final String serverId, final Server server) {
+ Vserver vserver = new Vserver();
+ vserver.setInMaint(false);
+ vserver.setIsClosedLoopDisabled(false);
+ vserver.setVserverId(serverId);
+ vserver.setVserverName(server.getName());
+ vserver.setVserverName2(server.getName());
+ vserver.setProvStatus(server.getStatus().value());
+ server.getLinks().stream().filter(link -> link.getRel().equals(HeatBridgeConstants.OS_RESOURCES_SELF_LINK_KEY))
+ .findFirst().ifPresent(link -> vserver.setVserverSelflink(link.getHref()));
+ return vserver;
+ }
+
+ /**
+ * Transform Openstack Image object to AAI Image object
+ *
+ * @param image Openstack Image object
+ * @return AAI Image object
+ */
+ public Image buildImage(final org.openstack4j.model.compute.Image image) {
+ Image aaiImage = new Image();
+ aaiImage.setImageId(image.getId());
+ aaiImage.setImageName(image.getName());
+ aaiImage.setImageOsDistro(HeatBridgeConstants.OS_UNKNOWN_KEY);
+ aaiImage.setImageOsVersion(HeatBridgeConstants.OS_UNKNOWN_KEY);
+ image.getLinks().stream().filter(link -> link.getRel().equals(HeatBridgeConstants.OS_RESOURCES_SELF_LINK_KEY))
+ .findFirst().ifPresent(link -> aaiImage.setImageSelflink(link.getHref()));
+ return aaiImage;
+ }
+
+ /**
+ * Transform Openstack Flavor object to AAI Flavor object
+ *
+ * @param flavor Openstack Flavor object
+ * @return AAI Flavor object
+ */
+ public Flavor buildFlavor(final org.openstack4j.model.compute.Flavor flavor) {
+ Flavor aaiFlavor = new Flavor();
+ aaiFlavor.setFlavorId(flavor.getId());
+ aaiFlavor.setFlavorName(flavor.getName());
+ flavor.getLinks().stream().filter(link -> link.getRel().equals(HeatBridgeConstants.OS_RESOURCES_SELF_LINK_KEY))
+ .findFirst().ifPresent(link -> aaiFlavor.setFlavorSelflink(link.getHref()));
+ return aaiFlavor;
+ }
+
+ /**
+ * Extract a list of flavors URI associated with the list of vservers
+ *
+ * @param vservers List of vserver AAI objects
+ * @return a list of related flavor related-links
+ */
+ public List<String> getFlavorsUriFromVserver(final List<Vserver> vservers) {
+ List<String> flavorUris = new ArrayList<>();
+ vservers.forEach(vserver -> flavorUris.addAll(
+ filterRelatedLinksByRelatedToProperty(vserver.getRelationshipList(), HeatBridgeConstants.AAI_FLAVOR)));
+ return flavorUris;
+ }
+
+ /**
+ * Extract a list of images URI associated with the list of vservers
+ *
+ * @param vservers List of vserver AAI objects
+ * @return a list of related image related-links
+ */
+ public List<String> getImagesUriFromVserver(final List<Vserver> vservers) {
+ List<String> imageUris = new ArrayList<>();
+ vservers.forEach(vserver -> imageUris.addAll(
+ filterRelatedLinksByRelatedToProperty(vserver.getRelationshipList(), HeatBridgeConstants.AAI_IMAGE)));
+ return imageUris;
+ }
+
+ /**
+ * From the list vserver objects build a map of compute hosts's name and the PCI IDs linked to it.
+ *
+ * @param vservers List of vserver AAI objects
+ * @return a map of compute names to the PCI ids associated with the compute
+ */
+ public Map<String, List<String>> getPserverToPciIdMap(final List<Vserver> vservers) {
+ Map<String, List<String>> pserverToPciIdMap = new HashMap<>();
+ for(Vserver vserver : vservers) {
+ if(vserver.getLInterfaces() != null) {
+ List<String> pciIds = vserver.getLInterfaces().getLInterface()
+ .stream()
+ .filter(lInterface -> lInterface.getSriovVfs() != null
+ && CollectionUtils.isNotEmpty(lInterface.getSriovVfs().getSriovVf()))
+ .flatMap(lInterface -> lInterface.getSriovVfs().getSriovVf().stream())
+ .map(SriovVf::getPciId)
+ .collect(Collectors.toList());
+ if (CollectionUtils.isNotEmpty(pciIds)) {
+ List<String> matchingPservers = extractRelationshipDataValue(vserver.getRelationshipList(),
+ HeatBridgeConstants.AAI_PSERVER, HeatBridgeConstants.AAI_PSERVER_HOSTNAME);
+ Preconditions.checkState(matchingPservers != null && matchingPservers.size() == 1,
+ "Invalid pserver relationships for vserver: " + vserver.getVserverName());
+ pserverToPciIdMap.put(matchingPservers.get(0), pciIds);
+ }
+ }
+ }
+ return pserverToPciIdMap;
+ }
+
+ /**
+ * Extract from relationship-list object all the relationship-value that match the related-to and
+ * relationship-key fields.
+ *
+ * @param relationshipListObj AAI relationship-list object
+ * @param relatedToProperty related-to value
+ * @param relationshipKey relationship-key value
+ * @return relationship-value matching the key requested for the relationship object of type related-to property
+ */
+ private List<String> extractRelationshipDataValue(final RelationshipList relationshipListObj,
+ final String relatedToProperty, final String relationshipKey) {
+ if (relationshipListObj != null && relationshipListObj.getRelationship() != null) {
+ return relationshipListObj.getRelationship().stream()
+ .filter(relationship -> relationship.getRelatedTo().equals(relatedToProperty))
+ .map(Relationship::getRelationshipData)
+ .flatMap(Collection::stream)
+ .filter(data -> data.getRelationshipKey() != null && relationshipKey.equals(data.getRelationshipKey()))
+ .map(RelationshipData::getRelationshipValue)
+ .collect(Collectors.toList());
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * Extract and filter the related-links to all objects that match the type specified by the filter property
+ *
+ * @param relationshipListObj AAI object representing relationship object
+ * @param relatedToProperty Value identifying the type of AAI object for related-to field
+ * @return a list of related-links filtered by the specified related-to property
+ */
+ private List<String> filterRelatedLinksByRelatedToProperty(final RelationshipList relationshipListObj,
+ final String relatedToProperty) {
+ if (relationshipListObj != null && relationshipListObj.getRelationship() != null) {
+ return relationshipListObj.getRelationship().stream()
+ .filter(relationship -> relationship.getRelatedTo().equals(relatedToProperty))
+ .map(Relationship::getRelatedLink)
+ .collect(Collectors.toList());
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * Build the relationship object
+ *
+ * @param relatedTo Related to entity value
+ * @param relationshipKeyValues Key value pairs of relationship data
+ * @return AAI Relationship object
+ */
+ private Relationship buildRelationship(final String relatedTo, final Map<String, String> relationshipKeyValues) {
+ Relationship relationship = new Relationship();
+ relationship.setRelatedTo(relatedTo);
+ relationshipKeyValues.keySet().forEach(k -> {
+ RelationshipData relationshipData = new RelationshipData();
+ relationshipData.setRelationshipKey(k);
+ relationshipData.setRelationshipValue(relationshipKeyValues.get(k));
+ relationship.getRelationshipData().add(relationshipData);
+ });
+ return relationship;
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackAccess.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackAccess.java
new file mode 100644
index 0000000000..fd5dabc784
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackAccess.java
@@ -0,0 +1,120 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+import org.openstack4j.model.common.Identifier;
+
+/**
+ * Object handling OpenStack API access information.
+ */
+public class OpenstackAccess {
+ private final String baseUrl;
+ private final String tenantId;
+ private final String user;
+ private final String password;
+ private final String region;
+ private String domainName;
+ private String projectName;
+
+ public OpenstackAccess(OpenstackAccessBuilder builder) {
+ this.baseUrl = builder.baseUrl;
+ this.tenantId = builder.tenantId;
+ this.user = builder.user;
+ this.password = builder.password;
+ this.region = builder.region;
+ this.domainName = builder.domainName;
+ this.projectName = builder.projectName;
+ }
+
+ public String getUrl() {
+ return baseUrl;
+ }
+
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public Identifier getDomainNameIdentifier() {
+ return Identifier.byName(domainName);
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public static class OpenstackAccessBuilder {
+
+ private String baseUrl;
+ private String tenantId;
+ private String user;
+ private String password;
+ private String region;
+ private String domainName;
+ private String projectName;
+
+ public OpenstackAccessBuilder setBaseUrl(final String baseUrl) {
+ this.baseUrl = baseUrl;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setTenantId(final String tenantId) {
+ this.tenantId = tenantId;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setUser(final String user) {
+ this.user = user;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setPassword(final String password) {
+ this.password = password;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setRegion(final String region) {
+ this.region = region;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setDomainName(final String domainName) {
+ this.domainName = domainName;
+ return this;
+ }
+
+ public OpenstackAccessBuilder setProjectName(final String projectName) {
+ this.projectName = projectName;
+ return this;
+ }
+
+ public OpenstackAccess build() {
+ return new OpenstackAccess(this);
+ }
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClient.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClient.java
new file mode 100644
index 0000000000..143e33581d
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClient.java
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+import java.util.List;
+import java.util.Map;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.heat.Resource;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+
+public interface OpenstackClient {
+
+ /**
+ * Get a server object by server ID
+ * @param serverId Unique server-name (simple name) or server-id (UUID)
+ * @return Server object
+ */
+ Server getServerById(String serverId);
+
+ /**
+ * Get a port object by port ID
+ * @param portId Unique UUID of the port.
+ * @return Port object.
+ */
+ Port getPortById(String portId);
+
+ /**
+ * Returns a list of all ports we have the right to see
+ * @return List of all Openstack ports
+ */
+ List<Port> getAllPorts();
+
+ /**
+ * Returns a list of all the resources for the stack
+ * @param stackId Stack name or unique UUID
+ * @param nestingDepth The recursion level for which resources will be listed.
+ * @return List of Openstack Stack resources
+ */
+ List<Resource> getStackBasedResources(String stackId, int nestingDepth);
+
+ /**
+ * Get a network instance by network ID
+ * @param networkId Unique UUID of the network.
+ * @return Network object.
+ */
+ Network getNetworkById(String networkId);
+
+ /**
+ * List networks by filtering parameters
+ * @param filterParams key-value pairs for filtering params
+ * @return List of filtered Network objects
+ */
+ List<Network> listNetworksByFilter(Map<String, String> filterParams);
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientException.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientException.java
new file mode 100644
index 0000000000..a062ca826d
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientException.java
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+public class OpenstackClientException extends Exception {
+ private static final long serialVersionUID = -5514207977226960180L;
+
+ public OpenstackClientException(final String message) {
+ super(message);
+ }
+
+ public OpenstackClientException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientImpl.java
new file mode 100644
index 0000000000..ebd4753323
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientImpl.java
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.openstack4j.api.OSClient;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.heat.Resource;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+
+abstract class OpenstackClientImpl implements OpenstackClient {
+ @Override
+ public Server getServerById(String serverId) {
+ return getClient().compute().servers().get(serverId);
+ }
+
+ @Override
+ public Port getPortById(String portId) {
+ return getClient().networking().port().get(portId);
+ }
+
+ @Override
+ public List<Port> getAllPorts() { return (List<Port>)getClient().networking().port().list(); }
+
+ @Override
+ public List<Resource> getStackBasedResources(String stackId, int nestingDepth) {
+ return getClient().heat()
+ .resources()
+ .list(stackId, nestingDepth)
+ .stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Network getNetworkById(String networkId) {
+ return getClient().networking().network().get(networkId);
+ }
+
+ @Override
+ public List<Network> listNetworksByFilter(Map<String, String> filterParams) {
+ return (List<Network>) getClient().networking().network().list(filterParams);
+ }
+
+ /**
+ * Retrieves the specific client to utilize.
+ * @return The specific client to utilize
+ */
+ protected abstract OSClient getClient();
+
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV2ClientImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV2ClientImpl.java
new file mode 100644
index 0000000000..760be72b3f
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV2ClientImpl.java
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+import org.openstack4j.api.OSClient;
+import org.openstack4j.api.OSClient.OSClientV2;
+
+public class OpenstackV2ClientImpl extends OpenstackClientImpl {
+
+ private OSClientV2 client;
+
+ public OpenstackV2ClientImpl(OSClientV2 client) {
+ this.client = client;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected OSClient getClient() {
+ return client;
+ }
+
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV3ClientImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV3ClientImpl.java
new file mode 100644
index 0000000000..dddd82ce6a
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV3ClientImpl.java
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.openstack.api;
+
+import org.openstack4j.api.OSClient;
+import org.openstack4j.api.OSClient.OSClientV3;
+
+public class OpenstackV3ClientImpl extends OpenstackClientImpl {
+
+ private OSClientV3 client;
+
+ public OpenstackV3ClientImpl(OSClientV3 client) {
+ this.client = client;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected OSClient getClient() {
+ return client;
+ }
+
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactory.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactory.java
new file mode 100644
index 0000000000..5019eec09b
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactory.java
@@ -0,0 +1,27 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.openstack.factory;
+
+import org.onap.so.heatbridge.openstack.api.OpenstackAccess;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.onap.so.heatbridge.openstack.api.OpenstackClientException;
+
+public interface OpenstackClientFactory {
+
+ OpenstackClient createOpenstackV3Client(OpenstackAccess osAccess) throws OpenstackClientException;
+
+ OpenstackClient createOpenstackV2Client(OpenstackAccess osAccess) throws OpenstackClientException;
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactoryImpl.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactoryImpl.java
new file mode 100644
index 0000000000..72b3795053
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactoryImpl.java
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge.openstack.factory;
+
+import com.google.common.base.Preconditions;
+import org.onap.so.heatbridge.openstack.api.OpenstackAccess;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.onap.so.heatbridge.openstack.api.OpenstackClientException;
+import org.onap.so.heatbridge.openstack.api.OpenstackV2ClientImpl;
+import org.onap.so.heatbridge.openstack.api.OpenstackV3ClientImpl;
+import org.openstack4j.api.OSClient.OSClientV2;
+import org.openstack4j.api.OSClient.OSClientV3;
+import org.openstack4j.api.exceptions.AuthenticationException;
+import org.openstack4j.openstack.OSFactory;
+
+public class OpenstackClientFactoryImpl implements OpenstackClientFactory {
+
+ @Override
+ public OpenstackClient createOpenstackV3Client(OpenstackAccess osAccess) throws OpenstackClientException {
+ Preconditions.checkNotNull(osAccess.getUrl(), "Keystone-v3 Auth: endpoint not set.");
+ Preconditions.checkNotNull(osAccess.getUser(), "Keystone-v3 Auth: username not set.");
+ Preconditions.checkNotNull(osAccess.getPassword(), "Keystone-v3 Auth: password not set.");
+ Preconditions.checkNotNull(osAccess.getDomainNameIdentifier(), "Keystone-v3 Auth: domain not set.");
+ Preconditions.checkNotNull(osAccess.getRegion(), "Keystone-v3 Auth: region not set.");
+
+ OSClientV3 client;
+ try {
+ client = OSFactory.builderV3()
+ .endpoint(osAccess.getUrl())
+ .credentials(osAccess.getUser(), osAccess.getPassword(), osAccess.getDomainNameIdentifier())
+ .authenticate()
+ .useRegion(osAccess.getRegion());
+ return new OpenstackV3ClientImpl(client);
+ } catch (AuthenticationException exception) {
+ throw new OpenstackClientException("Failed to authenticate with Keystone-v3: " + osAccess.getUrl(), exception);
+ }
+ }
+
+ @Override
+ public OpenstackClient createOpenstackV2Client(OpenstackAccess osAccess) throws OpenstackClientException {
+ Preconditions.checkNotNull(osAccess.getUrl(), "Keystone-v2 Auth: endpoint not set.");
+ Preconditions.checkNotNull(osAccess.getUser(), "Keystone-v2 Auth: username not set.");
+ Preconditions.checkNotNull(osAccess.getPassword(), "Keystone-v2 Auth: password not set.");
+ Preconditions.checkNotNull(osAccess.getTenantId(), "Keystone-v2 Auth: domain not set.");
+ Preconditions.checkNotNull(osAccess.getRegion(), "Keystone-v2 Auth: region not set.");
+
+ OSClientV2 client;
+ try {
+ client = OSFactory.builderV2()
+ .endpoint(osAccess.getUrl())
+ .credentials(osAccess.getUser(), osAccess.getPassword())
+ .tenantId(osAccess.getTenantId())
+ .authenticate()
+ .useRegion(osAccess.getRegion());
+ return new OpenstackV2ClientImpl(client);
+ } catch (AuthenticationException exception) {
+ throw new OpenstackClientException("Failed to authenticate with Keystone-v2.0: " + osAccess.getUrl(),
+ exception);
+ }
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/utils/HeatBridgeUtils.java b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/utils/HeatBridgeUtils.java
new file mode 100644
index 0000000000..7daa8c2c71
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/utils/HeatBridgeUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.so.heatbridge.utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.util.Optional;
+import javax.annotation.Nonnull;
+
+public class HeatBridgeUtils {
+
+ private HeatBridgeUtils() {
+ throw new IllegalStateException("Trying to instantiate a utility class.");
+ }
+
+ /**
+ * IaaS naming convention for compute/p-interface to openstack/physical-network name mapping
+ */
+ private static final String OS_SIDE_SHARED_SRIOV_PREFIX = "shared-";
+ private static final String OS_SIDE_DEDICATED_SRIOV_PREFIX = "dedicated-";
+ private static final String COMPUTE_SIDE_SHARED_SRIOV_PREFIX = "sriov-s-";
+ private static final String COMPUTE_SIDE_DEDICATED_SRIOV_PREFIX = "sriov-d-";
+
+ public static Optional<String> getMatchingPserverPifName(@Nonnull final String physicalNetworkName) {
+ Preconditions.checkState(!Strings.isNullOrEmpty(physicalNetworkName), "Physical network name is null or "
+ + "empty!");
+ if (physicalNetworkName.contains(OS_SIDE_DEDICATED_SRIOV_PREFIX)) {
+ return Optional
+ .of(physicalNetworkName.replace(OS_SIDE_DEDICATED_SRIOV_PREFIX, COMPUTE_SIDE_DEDICATED_SRIOV_PREFIX));
+ } else if (physicalNetworkName.contains(OS_SIDE_SHARED_SRIOV_PREFIX)) {
+ return Optional
+ .of(physicalNetworkName.replace(OS_SIDE_SHARED_SRIOV_PREFIX, COMPUTE_SIDE_SHARED_SRIOV_PREFIX));
+ }
+ return Optional.empty();
+ }
+
+ public static Optional<String> getMatchingPhysicalNetworkName(final String pserverPinterfaceName) {
+ if (pserverPinterfaceName.contains(COMPUTE_SIDE_DEDICATED_SRIOV_PREFIX)) {
+ return Optional
+ .of(pserverPinterfaceName.replace(COMPUTE_SIDE_DEDICATED_SRIOV_PREFIX, OS_SIDE_DEDICATED_SRIOV_PREFIX));
+ } else if (pserverPinterfaceName.contains(COMPUTE_SIDE_SHARED_SRIOV_PREFIX)) {
+ return Optional
+ .of(pserverPinterfaceName.replace(COMPUTE_SIDE_SHARED_SRIOV_PREFIX, OS_SIDE_SHARED_SRIOV_PREFIX));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/test/java/org/onap/so/heatbridge/HeatBridgeImplTest.java b/adapters/mso-openstack-adapters/src/test/java/org/onap/so/heatbridge/HeatBridgeImplTest.java
new file mode 100644
index 0000000000..3c777e17cb
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/test/java/org/onap/so/heatbridge/HeatBridgeImplTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2018 Bell Canada. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.so.heatbridge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.onap.aai.domain.yang.LInterface;
+import org.onap.aai.domain.yang.PInterface;
+import org.onap.aai.domain.yang.SriovPf;
+import org.onap.aai.domain.yang.Vserver;
+import org.onap.so.client.aai.AAIObjectType;
+import org.onap.so.client.aai.AAIResourcesClient;
+import org.onap.so.client.aai.AAISingleTransactionClient;
+import org.onap.so.client.aai.entities.uri.AAIResourceUri;
+import org.onap.so.client.aai.entities.uri.AAIUriFactory;
+import org.onap.so.client.graphinventory.exceptions.BulkProcessFailed;
+import org.onap.so.db.catalog.beans.CloudIdentity;
+import org.onap.so.heatbridge.constants.HeatBridgeConstants;
+import org.onap.so.heatbridge.openstack.api.OpenstackClient;
+import org.onap.so.heatbridge.openstack.api.OpenstackClientException;
+import org.openstack4j.model.compute.Flavor;
+import org.openstack4j.model.compute.Image;
+import org.openstack4j.model.compute.Server;
+import org.openstack4j.model.compute.Server.Status;
+import org.openstack4j.model.heat.Resource;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.openstack.heat.domain.HeatResource;
+import org.openstack4j.openstack.heat.domain.HeatResource.Resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class HeatBridgeImplTest {
+
+ private static final String CLOUD_OWNER = "CloudOwner";
+ private static final String REGION_ID = "RegionOne";
+ private static final String TENANT_ID = "7320ec4a5b9d4589ba7c4412ccfd290f";
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @Mock
+ private OpenstackClient osClient;
+
+ private CloudIdentity cloudIdentity = new CloudIdentity();
+
+ @Mock
+ private AAIResourcesClient resourcesClient;
+ @Mock
+ private AAISingleTransactionClient transaction;
+
+ private HeatBridgeImpl heatbridge;
+
+ @Before
+ public void setUp() throws HeatBridgeException, OpenstackClientException, BulkProcessFailed {
+
+ when(resourcesClient.beginSingleTransaction()).thenReturn(transaction);
+ heatbridge = new HeatBridgeImpl(resourcesClient, cloudIdentity, CLOUD_OWNER, REGION_ID, TENANT_ID);
+ }
+
+ @Ignore
+ @Test
+ public void testQueryNestedHeatStackResources() throws HeatBridgeException {
+ // Arrange
+ String heatStackId = "1234567";
+ List<Resource> expectedResourceList = (List<Resource>) extractTestStackResources();
+ when(osClient.getStackBasedResources(heatStackId, HeatBridgeConstants.OS_DEFAULT_HEAT_NESTING))
+ .thenReturn(expectedResourceList);
+
+ // Act
+ List<Resource> resourceList = heatbridge.queryNestedHeatStackResources(heatStackId);
+
+ // Assert
+ verify(osClient).getStackBasedResources(heatStackId, HeatBridgeConstants.OS_DEFAULT_HEAT_NESTING);
+ assertEquals(resourceList, expectedResourceList);
+ }
+
+ @Test
+ public void testExtractStackResourceIdsByResourceType() throws HeatBridgeException {
+ // Arrange
+ List<Resource> expectedResourceList = (List<Resource>) extractTestStackResources();
+ List<String> expectedServerIds = Arrays.asList("43c2159b-2c04-46ac-bda5-594110cae2d3",
+ "7cff109a-b2b7-4933-97b4-ec44a8365568");
+
+ // Act
+ List<String> serverIds = heatbridge
+ .extractStackResourceIdsByResourceType(expectedResourceList, HeatBridgeConstants.OS_SERVER_RESOURCE_TYPE);
+
+ // Assert
+ assertEquals(expectedServerIds, serverIds);
+ }
+
+ @Ignore
+ @Test
+ public void testGetAllOpenstackServers() {
+ // Arrange
+ List<Resource> stackResources = (List<Resource>) extractTestStackResources();
+
+ Server server1 = mock(Server.class);
+ Server server2 = mock(Server.class);
+ List<Server> expectedServers = Arrays.asList(server1, server2);
+
+ when(osClient.getServerById("43c2159b-2c04-46ac-bda5-594110cae2d3")).thenReturn(server1);
+ when(osClient.getServerById("7cff109a-b2b7-4933-97b4-ec44a8365568")).thenReturn(server2);
+
+ // Act
+ List<Server> servers = heatbridge.getAllOpenstackServers(stackResources);
+
+ // Assert
+ assertEquals(expectedServers, servers);
+ }
+
+ @Ignore
+ @Test
+ public void testExtractOpenstackImagesFromServers() {
+ // Arrange
+ Server server1 = mock(Server.class);
+ Server server2 = mock(Server.class);
+ List<Server> servers = Arrays.asList(server1, server2);
+
+ Image image1 = mock(Image.class);
+ Image image2 = mock(Image.class);
+ when(image1.getId()).thenReturn("1");
+ when(image2.getId()).thenReturn("1");
+ List<Image> expectedDistinctImages = Collections.singletonList(image1);
+
+ when(server1.getImage()).thenReturn(image1);
+ when(server2.getImage()).thenReturn(image2);
+
+ // Act
+ List<Image> images = heatbridge.extractOpenstackImagesFromServers(servers);
+
+ // Assert
+ assertEquals(expectedDistinctImages, images);
+ }
+
+ @Ignore
+ @Test
+ public void testExtractOpenstackFlavorsFromServers() {
+ // Arrange
+ Server server1 = mock(Server.class);
+ Server server2 = mock(Server.class);
+ List<Server> servers = Arrays.asList(server1, server2);
+
+ Flavor flavor1 = mock(Flavor.class);
+ Flavor flavor2 = mock(Flavor.class);
+ when(flavor1.getId()).thenReturn("1");
+ when(flavor2.getId()).thenReturn("2");
+ List<Flavor> expectedFlavors = Arrays.asList(flavor1, flavor2);
+
+ when(server1.getFlavor()).thenReturn(flavor1);
+ when(server2.getFlavor()).thenReturn(flavor2);
+
+ // Act
+ List<Flavor> flavors = heatbridge.extractOpenstackFlavorsFromServers(servers);
+
+ // Assert
+ assertEquals(expectedFlavors, flavors);
+ }
+
+ @Test
+ public void testUpdateVserversToAai() throws HeatBridgeException {
+ // Arrange
+ Server server1 = mock(Server.class);
+
+ when(server1.getId()).thenReturn("test-server1-id");
+ when(server1.getHypervisorHostname()).thenReturn("test-hypervisor");
+ when(server1.getName()).thenReturn("test-server1-name");
+ when(server1.getStatus()).thenReturn(Status.ACTIVE);
+ when(server1.getLinks()).thenReturn(new ArrayList<>());
+
+ Server server2 = mock(Server.class);
+ when(server2.getId()).thenReturn("test-server2-id");
+ when(server2.getHypervisorHostname()).thenReturn("test-hypervisor");
+ when(server2.getName()).thenReturn("test-server2-name");
+ when(server2.getStatus()).thenReturn(Status.ACTIVE);
+ when(server2.getLinks()).thenReturn(new ArrayList<>());
+
+ List<Server> servers = Arrays.asList(server1, server2);
+
+ Image image = mock(Image.class);
+ when(server1.getImage()).thenReturn(image);
+ when(server2.getImage()).thenReturn(image);
+ when(image.getId()).thenReturn("test-image-id");
+
+ Flavor flavor = mock(Flavor.class);
+ when(server1.getFlavor()).thenReturn(flavor);
+ when(server2.getFlavor()).thenReturn(flavor);
+ when(flavor.getId()).thenReturn("test-flavor-id");
+
+
+ // Act
+ heatbridge.buildAddVserversToAaiAction("test-genericVnf-id", "test-vfModule-id", servers);
+
+ // Assert
+ ArgumentCaptor<AAIResourceUri> captor = ArgumentCaptor.forClass(AAIResourceUri.class);
+ verify(transaction, times(2)).create(captor.capture(), any(Vserver.class));
+
+ List<AAIResourceUri> uris = captor.getAllValues();
+ assertEquals(AAIUriFactory.createResourceUri(
+ AAIObjectType.VSERVER, CLOUD_OWNER, REGION_ID, TENANT_ID, server1.getId()), uris.get(0));
+ assertEquals(AAIUriFactory.createResourceUri(
+ AAIObjectType.VSERVER, CLOUD_OWNER, REGION_ID, TENANT_ID, server2.getId()), uris.get(1));
+
+ }
+
+ @Test
+ public void testUpdateImagesToAai() throws HeatBridgeException {
+ // Arrange
+ Image image1 = mock(Image.class);
+ when(image1.getId()).thenReturn("test-image1-id");
+ when(image1.getName()).thenReturn("test-image1-name");
+ when(image1.getLinks()).thenReturn(new ArrayList<>());
+
+ Image image2 = mock(Image.class);
+ when(image2.getId()).thenReturn("test-image2-id");
+ when(image2.getName()).thenReturn("test-image2-name");
+ when(image2.getLinks()).thenReturn(new ArrayList<>());
+
+ List<Image> images = Arrays.asList(image1, image2);
+
+ // Act #1
+ heatbridge.buildAddImagesToAaiAction(images);
+
+ // Assert #1
+ verify(transaction, times(2)).create(any(AAIResourceUri.class), any(org.onap.aai.domain.yang.Image.class));
+
+ // Act #2
+ heatbridge.buildAddImagesToAaiAction(images);
+
+ // Assert #2
+ verify(transaction, times(4)).create(any(AAIResourceUri.class), any(org.onap.aai.domain.yang.Image.class));
+ }
+
+ @Test
+ public void testUpdateFlavorsToAai() throws HeatBridgeException {
+ // Arrange
+ Flavor flavor1 = mock(Flavor.class);
+ when(flavor1.getId()).thenReturn("test-flavor1-id");
+ when(flavor1.getName()).thenReturn("test-flavor1-name");
+ when(flavor1.getLinks()).thenReturn(new ArrayList<>());
+
+ Flavor flavor2 = mock(Flavor.class);
+ when(flavor2.getId()).thenReturn("test-flavor2-id");
+ when(flavor2.getName()).thenReturn("test-flavor2-name");
+ when(flavor2.getLinks()).thenReturn(new ArrayList<>());
+
+ List<Flavor> flavors = Arrays.asList(flavor1, flavor2);
+
+ // Act #1
+ heatbridge.buildAddFlavorsToAaiAction(flavors);
+
+ // Assert #1
+ verify(transaction, times(2)).create(any(AAIResourceUri.class), any(org.onap.aai.domain.yang.Flavor.class));
+
+ // Act #2
+ heatbridge.buildAddFlavorsToAaiAction(flavors);
+
+ // Assert #2
+ verify(transaction, times(4)).create(any(AAIResourceUri.class), any(org.onap.aai.domain.yang.Flavor.class));
+ }
+
+ @Ignore
+ @Test
+ public void testUpdateVserverLInterfacesToAai() throws HeatBridgeException {
+ // Arrange
+ List<Resource> stackResources = (List<Resource>) extractTestStackResources();
+ Port port = mock(Port.class);
+ when(port.getId()).thenReturn("test-port-id");
+ when(port.getName()).thenReturn("test-port-name");
+ when(port.getvNicType()).thenReturn(HeatBridgeConstants.OS_SRIOV_PORT_TYPE);
+ when(port.getMacAddress()).thenReturn("78:4f:43:68:e2:78");
+ when(port.getNetworkId()).thenReturn("890a203a-23gg-56jh-df67-731656a8f13a");
+ when(port.getDeviceId()).thenReturn("test-device-id");
+ when(port.getVifDetails())
+ .thenReturn(ImmutableMap.of(HeatBridgeConstants.OS_VLAN_NETWORK_KEY, "2345"));
+ String pfPciId = "0000:08:00.0";
+ when(port.getProfile()).thenReturn(ImmutableMap.of(HeatBridgeConstants.OS_PCI_SLOT_KEY, pfPciId,
+ HeatBridgeConstants.OS_PHYSICAL_NETWORK_KEY, "physical_network_id"));
+
+ Network network = mock(Network.class);
+ when(network.getId()).thenReturn("test-network-id");
+ when(network.getNetworkType()).thenReturn(NetworkType.VLAN);
+ when(network.getProviderSegID()).thenReturn("2345");
+
+ when(osClient.getPortById("212a203a-9764-4f42-84ea-731536a8f13a")).thenReturn(port);
+ when(osClient.getPortById("387e3904-8948-43d1-8635-b6c2042b54da")).thenReturn(port);
+ when(osClient.getPortById("70a09dfd-f1c5-4bc8-bd8f-dc539b8d662a")).thenReturn(port);
+ when(osClient.getPortById("12f88b4d-c8a4-4fbd-bcb4-7e36af02430b")).thenReturn(port);
+ when(osClient.getPortById("c54b9f45-b413-4937-bbe4-3c8a5689cfc9")).thenReturn(port);
+ when(osClient.getNetworkById(anyString())).thenReturn(network);
+
+ SriovPf sriovPf = new SriovPf();
+ sriovPf.setPfPciId(pfPciId);
+ PInterface pIf = mock(PInterface.class);
+ when(pIf.getInterfaceName()).thenReturn("test-port-id");
+ when(resourcesClient.get(eq(PInterface.class), any(AAIResourceUri.class))).thenReturn(Optional.of(pIf));
+
+ // Act
+ heatbridge.buildAddVserverLInterfacesToAaiAction(stackResources, Arrays.asList("1", "2"));
+
+ // Assert
+ verify(transaction, times(5)).create(any(AAIResourceUri.class), any(LInterface.class));
+ verify(osClient, times(5)).getPortById(anyString());
+ verify(osClient, times(5)).getNetworkById(anyString());
+ }
+
+ private List<? extends Resource> extractTestStackResources() {
+ List<HeatResource> stackResources = null;
+ try {
+ stackResources = MAPPER.readValue(readTestResourceFile("stack-resources.json"), Resources.class)
+ .getList();
+ assertNotNull(stackResources);
+ assertFalse(stackResources.isEmpty());
+ } catch (IOException e) {
+ Assert.fail("Failed to extract test stack resources.");
+ }
+ return stackResources;
+ }
+
+ private String readTestResourceFile(String filePath) {
+ String content = null;
+ String pathname = Objects.requireNonNull(getClass().getClassLoader().getResource(filePath)).getFile();
+ File file = new File(pathname);
+ try {
+ content = Objects.requireNonNull(FileUtils.readFileToString(file, Charset.defaultCharset()));
+ } catch (IOException e) {
+ Assert.fail(String.format("Failed to read test resource file (%s)", filePath));
+ }
+ return content;
+ }
+}
diff --git a/adapters/mso-openstack-adapters/src/test/resources/stack-resources.json b/adapters/mso-openstack-adapters/src/test/resources/stack-resources.json
new file mode 100644
index 0000000000..6b63895a33
--- /dev/null
+++ b/adapters/mso-openstack-adapters/src/test/resources/stack-resources.json
@@ -0,0 +1,441 @@
+{
+ "resources": [
+ {
+ "resource_name": "ge_000",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule/d1431cdc-9b29-44fc-98d0-9b3dc1ac246d/resources/ge_000",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule/d1431cdc-9b29-44fc-98d0-9b3dc1ac246d",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-ge_000-t66dxpwq6nb5/deee54a3-08ac-477b-9c09-c798edb40be1",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "ge_000",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:52Z",
+ "required_by": [
+ "vfw_instance"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "deee54a3-08ac-477b-9c09-c798edb40be1",
+ "resource_type": "port.yaml"
+ },
+ {
+ "resource_name": "vfw_instance",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule/d1431cdc-9b29-44fc-98d0-9b3dc1ac246d/resources/vfw_instance",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule/d1431cdc-9b29-44fc-98d0-9b3dc1ac246d",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "vfw_instance",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:52Z",
+ "required_by": [],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "resource_type": "vfw.yaml"
+ },
+ {
+ "resource_name": "port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-ge_000-t66dxpwq6nb5/deee54a3-08ac-477b-9c09-c798edb40be1/resources/port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-ge_000-t66dxpwq6nb5/deee54a3-08ac-477b-9c09-c798edb40be1",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "port",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:52Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "212a203a-9764-4f42-84ea-731536a8f13a",
+ "resource_type": "OS::Neutron::Port"
+ },
+ {
+ "resource_name": "pfe0",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/pfe0",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-pfe0-kvqmgn7jmiti/1325e04b-e836-4a13-bb2e-f34923d97ad7",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "pfe0",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "re0"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "1325e04b-e836-4a13-bb2e-f34923d97ad7",
+ "resource_type": "fpc.yaml"
+ },
+ {
+ "resource_name": "fpc_internal_port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/fpc_internal_port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_internal_port-gbnyc4w7mb5b/4e920f39-9784-417e-9331-d75e2e37cc51",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "fpc_internal_port",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "pfe0"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "4e920f39-9784-417e-9331-d75e2e37cc51",
+ "resource_type": "re_pfe_port.yaml"
+ },
+ {
+ "resource_name": "re-fpc-affinity-grp",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/re-fpc-affinity-grp",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "re-fpc-affinity-grp",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "pfe0",
+ "re0"
+ ],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "3aa37238-f8ff-4c96-b56a-8903bae28a60",
+ "resource_type": "OS::Nova::ServerGroup"
+ },
+ {
+ "resource_name": "re0",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/re0",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re0-73oifso3xntc/0915e27e-428d-4d2c-a67b-abbce18081b2",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "re0",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "0915e27e-428d-4d2c-a67b-abbce18081b2",
+ "resource_type": "re.yaml"
+ },
+ {
+ "resource_name": "re_external_port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/re_external_port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_external_port-3okiee3zocr7/f58c65e3-a72e-4b2d-a295-cb40324d6b4c",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "re_external_port",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "re0"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "f58c65e3-a72e-4b2d-a295-cb40324d6b4c",
+ "resource_type": "port.yaml"
+ },
+ {
+ "resource_name": "fpc_external_port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/fpc_external_port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_external_port-5vumcqp7hkbn/979e47c9-c15a-428e-ad73-af922029ee37",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "fpc_external_port",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "pfe0",
+ "re0"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "979e47c9-c15a-428e-ad73-af922029ee37",
+ "resource_type": "port.yaml"
+ },
+ {
+ "resource_name": "re_internal_port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/re_internal_port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_internal_port-u4txbvemndci/0aebfd9d-ad97-43b1-a67b-b2b5340738d2",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "re_internal_port",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "re0"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "0aebfd9d-ad97-43b1-a67b-b2b5340738d2",
+ "resource_type": "re_pfe_port.yaml"
+ },
+ {
+ "resource_name": "re_pfe_network",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b/resources/re_pfe_network",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam/54f93b9e-5138-4f3f-bfe0-ee06e1f0877b",
+ "rel": "stack"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_pfe_network-2wmjvgzrhtvs/290fc2fd-cd1d-47d0-90eb-2ece7c009b29",
+ "rel": "nested"
+ }
+ ],
+ "logical_resource_id": "re_pfe_network",
+ "resource_status_reason": "state changed",
+ "updated_time": "2018-04-09T21:09:54Z",
+ "required_by": [
+ "fpc_internal_port",
+ "re_internal_port"
+ ],
+ "resource_status": "CREATE_COMPLETE",
+ "physical_resource_id": "290fc2fd-cd1d-47d0-90eb-2ece7c009b29",
+ "resource_type": "bridge_int.yaml"
+ },
+ {
+ "resource_name": "fpc",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-pfe0-kvqmgn7jmiti/1325e04b-e836-4a13-bb2e-f34923d97ad7/resources/fpc",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-pfe0-kvqmgn7jmiti/1325e04b-e836-4a13-bb2e-f34923d97ad7",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "fpc",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:58Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "43c2159b-2c04-46ac-bda5-594110cae2d3",
+ "resource_type": "OS::Nova::Server"
+ },
+ {
+ "resource_name": "port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_internal_port-gbnyc4w7mb5b/4e920f39-9784-417e-9331-d75e2e37cc51/resources/port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_internal_port-gbnyc4w7mb5b/4e920f39-9784-417e-9331-d75e2e37cc51",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "port",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:56Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "387e3904-8948-43d1-8635-b6c2042b54da",
+ "resource_type": "OS::Neutron::Port"
+ },
+ {
+ "resource_name": "re",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re0-73oifso3xntc/0915e27e-428d-4d2c-a67b-abbce18081b2/resources/re",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re0-73oifso3xntc/0915e27e-428d-4d2c-a67b-abbce18081b2",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "re",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:10:36Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "7cff109a-b2b7-4933-97b4-ec44a8365568",
+ "resource_type": "OS::Nova::Server"
+ },
+ {
+ "resource_name": "port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_external_port-3okiee3zocr7/f58c65e3-a72e-4b2d-a295-cb40324d6b4c/resources/port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_external_port-3okiee3zocr7/f58c65e3-a72e-4b2d-a295-cb40324d6b4c",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "port",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:55Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "70a09dfd-f1c5-4bc8-bd8f-dc539b8d662a",
+ "resource_type": "OS::Neutron::Port"
+ },
+ {
+ "resource_name": "port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_external_port-5vumcqp7hkbn/979e47c9-c15a-428e-ad73-af922029ee37/resources/port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-fpc_external_port-5vumcqp7hkbn/979e47c9-c15a-428e-ad73-af922029ee37",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "port",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:55Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "12f88b4d-c8a4-4fbd-bcb4-7e36af02430b",
+ "resource_type": "OS::Neutron::Port"
+ },
+ {
+ "resource_name": "port",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_internal_port-u4txbvemndci/0aebfd9d-ad97-43b1-a67b-b2b5340738d2/resources/port",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_internal_port-u4txbvemndci/0aebfd9d-ad97-43b1-a67b-b2b5340738d2",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "port",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:56Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "c54b9f45-b413-4937-bbe4-3c8a5689cfc9",
+ "resource_type": "OS::Neutron::Port"
+ },
+ {
+ "resource_name": "bridge_network_subnet",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_pfe_network-2wmjvgzrhtvs/290fc2fd-cd1d-47d0-90eb-2ece7c009b29/resources/bridge_network_subnet",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_pfe_network-2wmjvgzrhtvs/290fc2fd-cd1d-47d0-90eb-2ece7c009b29",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "bridge_network_subnet",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:55Z",
+ "required_by": [],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "5ffd8c02-6913-4b67-adba-74e78c2bbe40",
+ "resource_type": "OS::Neutron::Subnet"
+ },
+ {
+ "resource_name": "bridge_network",
+ "links": [
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_pfe_network-2wmjvgzrhtvs/290fc2fd-cd1d-47d0-90eb-2ece7c009b29/resources/bridge_network",
+ "rel": "self"
+ },
+ {
+ "href": "http://10.10.10.10:8004/v1/7320ec4a5b9d4589ba7c4412ccfd290f/stacks/ClosedLoop_vFW_VfModule-vfw_instance-tw3i5ile2nam-re_pfe_network-2wmjvgzrhtvs/290fc2fd-cd1d-47d0-90eb-2ece7c009b29",
+ "rel": "stack"
+ }
+ ],
+ "logical_resource_id": "bridge_network",
+ "resource_status": "CREATE_COMPLETE",
+ "updated_time": "2018-04-09T21:09:55Z",
+ "required_by": [
+ "bridge_network_subnet"
+ ],
+ "resource_status_reason": "state changed",
+ "physical_resource_id": "5ad95036-8daf-4379-a59c-865f35976cd4",
+ "resource_type": "OS::Neutron::Net"
+ }
+ ]
+}