heatbridge implementation for openstack-adapter 55/78155/12
authorMunir Ahmad <munir.ahmad@bell.ca>
Fri, 8 Feb 2019 21:42:03 +0000 (16:42 -0500)
committerMunir Ahmad <munir.ahmad@bell.ca>
Sun, 24 Mar 2019 21:36:27 +0000 (17:36 -0400)
Converted heatbridge to use AAI Client
Removed open feign
Removed new aai client implementation
Removed aai helper classes
Removed unnecessary pom.xml imports
converted HeatBridgeImpl to rely on AAI transactions
added some example unit tests
Replaced MsoLogger with slf4j
Resolved merge conflicts

Change-Id: I933b49fae82c2f0d7a66d9a85b367b29006c73cc
Issue-ID: SO-1484
Signed-off-by: Benjamin, Max (mb388a) <mb388a@us.att.com>
Change-Id: I547b35ebdf51f4534de5d51d7d50a90bb9de4c72
Signed-off-by: Munir Ahmad <munir.ahmad@bell.ca>
Issue-ID: SO-1484

21 files changed:
adapters/mso-openstack-adapters/pom.xml
adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/vnf/MsoVnfAdapterImpl.java
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeApi.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeException.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/HeatBridgeImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/constants/HeatBridgeConstants.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactory.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/factory/MsoCloudClientFactoryImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/helpers/AaiHelper.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackAccess.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClient.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientException.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackClientImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV2ClientImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/api/OpenstackV3ClientImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactory.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/openstack/factory/OpenstackClientFactoryImpl.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/main/java/org/onap/so/heatbridge/utils/HeatBridgeUtils.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/test/java/org/onap/so/heatbridge/HeatBridgeImplTest.java [new file with mode: 0644]
adapters/mso-openstack-adapters/src/test/resources/stack-resources.json [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/AAIObjectType.java

index cb35e90..73f50ed 100644 (file)
        <packaging>jar</packaging>
        <name>mso-openstack-adapters</name>
        <description>Consolidate openstack adapters into one Spring Boot project</description>
-
+       <properties>
+               <openfeign.version>10.1.0</openfeign.version>
+       </properties>
        <build>
                <finalName>${project.artifactId}-${project.version}</finalName>
-               
-               <plugins>                       
+
+               <plugins>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-dependency-plugin</artifactId>
                <artifactId>janino</artifactId>
                <version>2.5.15</version>
                </dependency>
-        
-       <!-- end added for spring boot support -->      
-       
-       
-       
-       <!-- added for unit testing -->         
+
+       <!-- end added for spring boot support -->
+
+               <dependency>
+                       <groupId>org.pacesys</groupId>
+                       <artifactId>openstack4j-core</artifactId>
+                       <version>3.1.0</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.pacesys.openstack4j.connectors</groupId>
+                       <artifactId>openstack4j-httpclient</artifactId>
+                       <version>3.1.0</version>
+               </dependency>
+
+               <dependency>
+                       <groupId>commons-collections</groupId>
+                       <artifactId>commons-collections</artifactId>
+                       <version>3.2.1</version>
+               </dependency>
+
+               <dependency>
+                       <groupId>com.typesafe</groupId>
+                       <artifactId>config</artifactId>
+                       <version>1.3.2</version>
+               </dependency>
+               <dependency>
+                       <groupId>com.google.code.findbugs</groupId>
+                       <artifactId>jsr305</artifactId>
+                       <version>1.3.9</version>
+               </dependency>
+               
+               <dependency>
+                       <groupId>commons-validator</groupId>
+                       <artifactId>commons-validator</artifactId>
+                       <version>1.4.0</version>
+               </dependency>
+
+       <!-- added for unit testing -->
                <dependency>
                        <groupId>org.onap.so.adapters</groupId>
                        <artifactId>mso-adapter-utils</artifactId>
index 3913d7f..949027f 100644 (file)
@@ -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 (file)
index 0000000..6b06761
--- /dev/null
@@ -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 (file)
index 0000000..f993d71
--- /dev/null
@@ -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 (file)
index 0000000..90ceeb7
--- /dev/null
@@ -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 (file)
index 0000000..1f30234
--- /dev/null
@@ -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 (file)
index 0000000..100b50e
--- /dev/null
@@ -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 (file)
index 0000000..b70b32a
--- /dev/null
@@ -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 (file)
index 0000000..a0f1f07
--- /dev/null
@@ -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 (file)
index 0000000..fd5dabc
--- /dev/null
@@ -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 (file)
index 0000000..143e335
--- /dev/null
@@ -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 (file)
index 0000000..a062ca8
--- /dev/null
@@ -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 (file)
index 0000000..ebd4753
--- /dev/null
@@ -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 (file)
index 0000000..760be72
--- /dev/null
@@ -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 (file)
index 0000000..dddd82c
--- /dev/null
@@ -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 (file)
index 0000000..5019eec
--- /dev/null
@@ -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 (file)
index 0000000..72b3795
--- /dev/null
@@ -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 (file)
index 0000000..7daa8c2
--- /dev/null
@@ -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 (file)
index 0000000..3c777e1
--- /dev/null
@@ -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 (file)
index 0000000..6b63895
--- /dev/null
@@ -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"
+    }
+  ]
+}
index 21e36cd..34d5602 100644 (file)
@@ -21,7 +21,6 @@
 package org.onap.so.client.aai;
 
 import java.io.Serializable;
-import java.lang.reflect.Field;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
@@ -39,7 +38,9 @@ import org.onap.aai.domain.yang.Connector;
 import org.onap.aai.domain.yang.Customer;
 import org.onap.aai.domain.yang.Device;
 import org.onap.aai.domain.yang.ExtAaiNetwork;
+import org.onap.aai.domain.yang.Flavor;
 import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.aai.domain.yang.Image;
 import org.onap.aai.domain.yang.InstanceGroup;
 import org.onap.aai.domain.yang.L3Network;
 import org.onap.aai.domain.yang.LInterface;
@@ -134,6 +135,8 @@ public class AAIObjectType implements GraphInventoryObjectType, Serializable {
        public static final AAIObjectType EXT_AAI_NETWORK = new AAIObjectType(AAINamespaceConstants.NETWORK, ExtAaiNetwork.class);
        public static final AAIObjectType AGGREGATE_ROUTE = new AAIObjectType(AAINamespaceConstants.NETWORK, AggregateRoute.class);
        public static final AAIObjectType L_INTERFACE = new AAIObjectType(AAIObjectType.VSERVER.uriTemplate(), LInterface.class);
+       public static final AAIObjectType IMAGE = new AAIObjectType(AAIObjectType.CLOUD_REGION.uriTemplate(), Image.class);
+       public static final AAIObjectType FLAVOR = new AAIObjectType(AAIObjectType.CLOUD_REGION.uriTemplate(), Flavor.class);
        public static final AAIObjectType UNKNOWN = new AAIObjectType("", "", "unknown");
        public static final AAIObjectType DSL = new AAIObjectType("/dsl", "", "dsl");