Merge "error as failed to extract workflow"
authorSteve Smokowski <ss835w@att.com>
Mon, 25 Mar 2019 12:10:22 +0000 (12:10 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 25 Mar 2019 12:10:22 +0000 (12:10 +0000)
26 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]
adapters/mso-vnfm-adapter/mso-vnfm-etsi-adapter/src/main/java/org/onap/so/adapters/vnfmadapter/rest/VnfmAdapterController.java
adapters/mso-vnfm-adapter/mso-vnfm-etsi-adapter/src/test/java/org/onap/so/adapters/vnfmadapter/rest/VnfmAdapterControllerTest.java
bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/sdnc/mapper/GCTopologyOperationRequestMapper.java
bpmn/so-bpmn-tasks/src/test/java/org/onap/so/client/sdnc/mapper/GCTopologyOperationRequestMapperTest.java
common/src/main/java/org/onap/so/client/aai/AAIObjectType.java
mso-catalog-db/src/main/java/org/onap/so/db/catalog/client/CatalogDbClient.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 77a3f21..b14ead0 100644 (file)
@@ -27,12 +27,14 @@ import javax.ws.rs.core.MediaType;
 import org.onap.logging.ref.slf4j.ONAPLogConstants;
 import org.onap.vnfmadapter.v1.model.CreateVnfRequest;
 import org.onap.vnfmadapter.v1.model.CreateVnfResponse;
+import org.onap.vnfmadapter.v1.model.DeleteVnfResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -58,15 +60,15 @@ public class VnfmAdapterController {
                     required = true) @Valid @RequestBody final CreateVnfRequest createVnfRequest,
             @ApiParam(
                     value = "Used to track REST requests for logging purposes. Identifies a single top level invocation of ONAP",
-                    required = true) @RequestHeader(value = ONAPLogConstants.Headers.REQUEST_ID,
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.REQUEST_ID,
                             required = false) final String requestId,
             @ApiParam(
                     value = "Used to track REST requests for logging purposes. Identifies the client application user agent or user invoking the API",
-                    required = true) @RequestHeader(value = ONAPLogConstants.Headers.PARTNER_NAME,
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.PARTNER_NAME,
                             required = false) final String partnerName,
             @ApiParam(
                     value = "Used to track REST requests for logging purposes. Identifies a single invocation of a single component",
-                    required = true) @RequestHeader(value = ONAPLogConstants.Headers.INVOCATION_ID,
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.INVOCATION_ID,
                             required = false) final String invocationId) {
 
         setLoggingMDCs(requestId, partnerName, invocationId);
@@ -79,6 +81,33 @@ public class VnfmAdapterController {
         return new ResponseEntity<>(response, HttpStatus.ACCEPTED);
     }
 
+    @DeleteMapping(value = "/vnfs/{vnfId}")
+    public ResponseEntity<DeleteVnfResponse> vnfDelete(
+            @ApiParam(value = "The identifier of the VNF. This must be the vnf-id of an existing generic-vnf in AAI.",
+                    required = true) @PathVariable("vnfId") final String vnfId,
+            @ApiParam(
+                    value = "Used to track REST requests for logging purposes. Identifies a single top level invocation of ONAP",
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.REQUEST_ID,
+                            required = false) final String requestId,
+            @ApiParam(
+                    value = "Used to track REST requests for logging purposes. Identifies the client application user agent or user invoking the API",
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.PARTNER_NAME,
+                            required = false) final String partnerName,
+            @ApiParam(
+                    value = "Used to track REST requests for logging purposes. Identifies a single invocation of a single component",
+                    required = false) @RequestHeader(value = ONAPLogConstants.Headers.INVOCATION_ID,
+                            required = false) final String invocationId) {
+
+        setLoggingMDCs(requestId, partnerName, invocationId);
+
+        logger.info("REST request vnfDelete for VNF: {}", vnfId);
+
+        final DeleteVnfResponse response = new DeleteVnfResponse();
+        response.setJobId(UUID.randomUUID().toString());
+        clearLoggingMDCs();
+        return new ResponseEntity<>(response, HttpStatus.ACCEPTED);
+    }
+
     private void setLoggingMDCs(final String requestId, final String partnerName, final String invocationId) {
         MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId);
         MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, partnerName);
index 842b3b5..071a330 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
 import org.onap.so.adapters.vnfmadapter.VnfmAdapterApplication;
 import org.onap.vnfmadapter.v1.model.CreateVnfRequest;
 import org.onap.vnfmadapter.v1.model.CreateVnfResponse;
+import org.onap.vnfmadapter.v1.model.DeleteVnfResponse;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
 import org.springframework.boot.test.web.client.TestRestTemplate;
@@ -75,4 +76,15 @@ public class VnfmAdapterControllerTest {
         assertEquals(401, response.getStatusCode().value());
     }
 
+    @Test
+    public void deleteVnf_ValidRequest_Returns202AndJobId() throws Exception {
+        final RequestEntity<Void> request = RequestEntity
+                .delete(new URI("http://localhost:" + port + "/so/vnfm-adapter/v1/vnfs/myVnfId"))
+                .accept(MediaType.APPLICATION_JSON).header("X-ONAP-RequestId", "myRequestId")
+                .header("X-ONAP-InvocationID", "myInvocationId").header("Content-Type", "application/json").build();
+        final ResponseEntity<DeleteVnfResponse> response = restTemplate.exchange(request, DeleteVnfResponse.class);
+        assertEquals(202, response.getStatusCode().value());
+        assertNotNull(response.getBody().getJobId());
+    }
+
 }
index e46c456..b11e2ca 100644 (file)
@@ -85,6 +85,7 @@ public class GCTopologyOperationRequestMapper {
         GenericResourceApiConfigurationinformationConfigurationInformation configurationInformation =
                 new GenericResourceApiConfigurationinformationConfigurationInformation();
         configurationInformation.setConfigurationId(vnrConfiguration.getConfigurationId());
+        configurationInformation.setConfigurationType(vnrConfiguration.getConfigurationType());
         req.setRequestInformation(requestInformation);
         req.setSdncRequestHeader(sdncRequestHeader);
         req.setServiceInformation(serviceInformation);
index f4d442b..0ca80c7 100644 (file)
@@ -62,6 +62,7 @@ public class GCTopologyOperationRequestMapperTest extends TestDataSetup {
         serviceInstance.setServiceInstanceId("ServiceInstanceId");
         Configuration Configuration = new Configuration();
         Configuration.setConfigurationId("ConfigurationId");
+        Configuration.setConfigurationType("VLAN-NETWORK-RECEPTOR");
         GenericResourceApiGcTopologyOperationInformation genericInfo = genObjMapper.deactivateOrUnassignVnrReqMapper
                 (SDNCSvcAction.UNASSIGN, serviceInstance, requestContext, Configuration,"uuid",new URI("http://localhost"));
 
@@ -70,6 +71,8 @@ public class GCTopologyOperationRequestMapperTest extends TestDataSetup {
         Assert.assertNotNull(genericInfo.getSdncRequestHeader());
         Assert.assertNotNull(genericInfo.getClass());
         Assert.assertNotNull(genericInfo.getServiceInformation());
+        Assert.assertEquals("ConfigurationId", genericInfo.getConfigurationInformation().getConfigurationId());
+        Assert.assertEquals("VLAN-NETWORK-RECEPTOR", genericInfo.getConfigurationInformation().getConfigurationType());
         Assert.assertEquals("uuid",genericInfo.getSdncRequestHeader().getSvcRequestId()); 
         Assert.assertEquals("http://localhost",genericInfo.getSdncRequestHeader().getSvcNotificationUrl());
     }
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");
 
index 2faac31..3e35e78 100644 (file)
@@ -808,10 +808,12 @@ public class CatalogDbClient {
     public VnfVfmoduleCvnfcConfigurationCustomization getVnfVfmoduleCvnfcConfigurationCustomizationByVnfCustomizationUuidAndVfModuleCustomizationUuidAndCvnfcCustomizationUuid(
             String vnfCustomizationUuid, String vfModuleCustomizationUuid, String cvnfcCustomizationUuid) {
         CvnfcCustomization cvnfc = getCvnfcCustomizationByCustomizationUUID(cvnfcCustomizationUuid);
-        for(VnfVfmoduleCvnfcConfigurationCustomization vnfVfModuleCvnfcCust: cvnfc.getVnfVfmoduleCvnfcConfigurationCustomization()){
-            if(vnfVfModuleCvnfcCust.getVnfResourceCustomization().getModelCustomizationUUID().equals(vnfCustomizationUuid) &&
+        if (cvnfc != null) {
+            for(VnfVfmoduleCvnfcConfigurationCustomization vnfVfModuleCvnfcCust: cvnfc.getVnfVfmoduleCvnfcConfigurationCustomization()){
+                if(vnfVfModuleCvnfcCust.getVnfResourceCustomization().getModelCustomizationUUID().equals(vnfCustomizationUuid) &&
                     vnfVfModuleCvnfcCust.getVfModuleCustomization().getModelCustomizationUUID().equals(vfModuleCustomizationUuid)){
                 return vnfVfModuleCvnfcCust;
+                }
             }
         }
         return null;