Integrate VNF Repository with SDC 33/19033/11
authorMurali-P <murali.p@huawei.com>
Mon, 16 Oct 2017 09:37:16 +0000 (15:07 +0530)
committerMurali-P <murali.p@huawei.com>
Mon, 4 Dec 2017 05:34:54 +0000 (11:04 +0530)
Add Browse  VNF packages feature

Change-Id: I0c721829efdac8ad6f72c4ac9d25ec96e091f7ca
Issue-ID: VNFSDK-82
Signed-off-by: Michael Lando <ml636r@att.com>
Signed-off-by: Murali-P <murali.p@huawei.com>
31 files changed:
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java [new file with mode: 0644]
openecomp-be/configuration/config-vnfsdk.yaml [new file with mode: 0644]
openecomp-ui/resources/scss/_components.scss
openecomp-ui/resources/scss/components/_vnfBrowse.scss [new file with mode: 0644]
openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx
openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx [new file with mode: 0644]
openecomp-ui/src/nfvo-utils/RestAPIUtil.js
openecomp-ui/src/nfvo-utils/i18n/en.json
openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
openecomp-ui/src/sdc-app/config/config.json
openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx [new file with mode: 0644]
sdc-os-chef/environments/Template.json
sdc-os-chef/pom.xml
sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_1_cleanup_jettydir.rb
sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_2_setup_configuration.rb
sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/BE-vnfrepo-configuration.yaml.erb [new file with mode: 0644]
sdc-os-chef/sdc-backend/startup.sh

index 9808d76..ee1b9b3 100644 (file)
             <artifactId>vendor-software-products-rest-services</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc.onboarding</groupId>
+            <artifactId>vnf-repository-rest-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.openecomp.sdc.onboarding</groupId>
             <artifactId>validation-rest-services</artifactId>
index 126f781..fb2db2b 100644 (file)
@@ -76,6 +76,7 @@
             <ref bean="deploymentFlavors"/>
                <ref bean="images"/>
             <ref bean="orchestrationTemplateCandidate"/>
+           <ref bean="vnfPackageRepository"/>
             <ref bean="componentDependencyModel"/>
             <ref bean="activityLog"/>
             <ref bean="healthCheck"/>
index eb7fe70..e311a7c 100644 (file)
@@ -17,5 +17,6 @@
     <modules>
         <module>/vendor-software-products-rest-services</module>
         <module>/vendor-software-products-rest-types</module>
+       <module>/vnf-repository-rest-services</module>
     </modules>
 </project>
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml
new file mode 100644 (file)
index 0000000..47ed01d
--- /dev/null
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+
+       <artifactId>vnf-repository-rest-services</artifactId>
+
+       <parent>
+               <groupId>org.openecomp.sdc.onboarding</groupId>
+               <artifactId>vendor-software-products-rest</artifactId>
+               <version>1.1.0-SNAPSHOT</version>
+       </parent>
+
+       <dependencies>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-core</artifactId>
+                       <version>${spring.framework.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-context</artifactId>
+                       <version>${spring.framework.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-context-support</artifactId>
+                       <version>${spring.framework.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-web</artifactId>
+                       <version>${spring.framework.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-beans</artifactId>
+                       <version>${spring.framework.version}</version>
+               </dependency>
+
+               <!-- CXF -->
+               <dependency>
+                       <groupId>org.apache.cxf</groupId>
+                       <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+                       <version>${cxf.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.httpcomponents</groupId>
+                       <artifactId>httpclient</artifactId>
+                       <version>${http.client.version}</version>
+               </dependency>
+               
+
+               <!-- Java Stuff -->
+               <dependency>
+                       <groupId>javax.inject</groupId>
+                       <artifactId>javax.inject</artifactId>
+                       <version>${javax.inject.version}</version>
+                       <scope>provided</scope>
+               </dependency>                           
+               <dependency>
+                       <groupId>org.openecomp.sdc</groupId>
+                       <artifactId>openecomp-sdc-common-rest</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.openecomp.sdc</groupId>
+                       <artifactId>common-app-api</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.openecomp.sdc.common</groupId>
+                       <artifactId>openecomp-configuration-management-api</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.openecomp.sdc.common</groupId>
+                       <artifactId>openecomp-configuration-management-core</artifactId>
+                       <version>${project.version}</version>
+                       <scope>runtime</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.openecomp.sdc.onboarding</groupId>
+                       <artifactId>vendor-software-products-rest-services</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+
+       </dependencies>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-surefire-plugin</artifactId>
+                               <configuration>
+                                       <includes>
+                                               <include>test/core/unittest/offline/**</include>
+                                       </includes>
+                               </configuration>
+                       </plugin>                       
+               </plugins>
+       </build>
+
+</project>
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java
new file mode 100644 (file)
index 0000000..1d922a6
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.openecomp.sdcrests.vsp.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.openecomp.sdcrests.vendorsoftwareproducts.types.UploadFileResponseDto;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.File;
+import static org.openecomp.sdcrests.common.RestConstants.USER_ID_HEADER_PARAM;
+import static org.openecomp.sdcrests.common.RestConstants.USER_MISSING_ERROR_MSG;
+
+@Path("/v1.0/vendor-software-products/{vspId}/versions/{versionId}/vnfrepository")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@Api(value = "VNF Repository packages")
+@Validated
+public interface VnfPackageRepository extends VspEntities {
+
+       @GET
+       @Path("/vnfpackages")
+       @Produces(MediaType.APPLICATION_OCTET_STREAM)
+       @ApiOperation(value = "Get VNF packages from VNF Repository", notes = "Call VNF Repostory to get VNF package details", response = File.class)
+       Response getVnfPackages(@PathParam("vspId") String vspId,
+                       @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+                       @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+       @GET
+       @Path("/vnfpackage/{csarId}/download")
+       @Produces(MediaType.APPLICATION_OCTET_STREAM)
+       @ApiOperation(value = "Download VNF package from VNF Repository", notes = "Download VNF package from VNF repository and send to client", response = File.class)
+       Response downloadVnfPackage(@PathParam("vspId") String vspId,
+                       @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+                       @PathParam("csarId") String csarId,
+                       @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+       @POST
+       @Path("/vnfpackage/{csarId}/import")
+       @Produces(MediaType.APPLICATION_JSON)
+       @ApiOperation(value = "Import VNF package from VNF Repository", notes = "Call VNF Repostory to download VNF package, validate it and send the response", response = UploadFileResponseDto.class)
+       Response importVnfPackage(@PathParam("vspId") String vspId,
+                       @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+                       @PathParam("csarId") String csarId,
+                       @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java
new file mode 100644 (file)
index 0000000..acbfb32
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.openecomp.sdcrests.vsp.rest.services;
+
+import org.apache.http.HttpStatus;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.logging.messages.AuditMessages;
+import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManager;
+import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManagerFactory;
+import org.openecomp.sdc.vendorsoftwareproduct.types.UploadFileResponse;
+import org.openecomp.sdc.versioning.types.VersionableEntityAction;
+import org.openecomp.sdcrests.vendorsoftwareproducts.types.UploadFileResponseDto;
+import org.openecomp.sdcrests.vsp.rest.VnfPackageRepository;
+import org.openecomp.sdcrests.vsp.rest.mapping.MapUploadFileResponseToUploadFileResponseDto;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+import org.openecomp.sdc.common.rest.api.IRestClient;
+import org.openecomp.sdc.common.rest.api.RestConfigurationInfo;
+import org.openecomp.sdc.common.rest.api.RestResponse;
+import org.openecomp.sdc.common.rest.impl.RestClientServiceFactory;
+import org.openecomp.config.api.Configuration;
+import org.openecomp.config.api.ConfigurationManager;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.inject.Named;
+import javax.ws.rs.core.Response;
+
+import static org.openecomp.core.utilities.file.FileUtils.getFileExtension;
+import static org.openecomp.core.utilities.file.FileUtils.getNetworkPackageName;
+
+/**
+ * 
+ * The class implements the API interface with VNF Repository (VNFSDK) such as
+ * i) Get all the VNF Package Meta-data ii) Download the VNF Package iii) Import
+ * VNF package to SDC catalog (Download & validate)
+ * 
+ * @version Amsterdam release (ONAP 1.0)
+ * 
+ */
+@Named
+@Service("vnfPackageRepository")
+@Scope(value = "prototype")
+public class VnfPackageRepositoryImpl implements VnfPackageRepository {
+
+       private static final Logger LOGGER = LoggerFactory.getLogger(VnfPackageRepositoryImpl.class);
+       private static IRestClient iRestClnt = null;
+       private static boolean initFlag = false;
+
+       // Default VNF Repository configuration
+       private static final String CONFIG_NAMESPACE = "vnfsdk";
+
+       // Default address for VNF repository docker
+       private static final String DEF_DOCKER_COMPOSE_ADDR = "172.18.0.1";
+       private static String ipAddress = DEF_DOCKER_COMPOSE_ADDR;
+
+       // Default Download package URI and Get VNF package meta-data URI -
+       // configurable
+       private static String getVnfPkgUri = "/onapapi/vnfsdk-marketplace/v1/PackageResource/csars";
+       private static String downldPkgUri = "/onapapi/vnfsdk-marketplace/v1/PackageResource/csars/%s/files";
+
+       // Default port for VNF Repository
+       private static String port = "8702";
+
+       @Override
+       public Response getVnfPackages(String vspId, String versionId, String user) throws Exception {
+
+               LOGGER.audit(AuditMessages.AUDIT_MSG, "Get VNF Packages from Repository:{}", vspId);
+
+               // Step 1: Create REST client and configuration and prepare URI
+               init();
+
+               // Step 2: Build URI based on the IP address and port allocated
+               RestResponse rsp = iRestClnt.doGET(getVnfPkgUri, null);
+               if (HttpStatus.SC_OK != rsp.getHttpStatusCode()) {
+                       LOGGER.error("Failed to query VNF package metadata:uri={}, Response={}", getVnfPkgUri, rsp);
+                       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+               }
+
+               // Step 3: Send the response to the client
+               LOGGER.debug("Response from VNF Repository: {}", rsp.getResponse());
+
+               return Response.ok(rsp.getResponse()).build();
+       }
+
+       @Override
+       public Response importVnfPackage(String vspId, String versionId, String csarId, String user) throws Exception {
+
+               LOGGER.audit(AuditMessages.AUDIT_MSG, "Import VNF Packages from Repository:{}", csarId);
+
+               // Step 1: Create REST client and configuration and prepare URI
+               init();
+
+               // Step 2: Build URI based on the IP address and port allocated
+               String uri = String.format(downldPkgUri, csarId);
+               RestResponse rsp = iRestClnt.doGET(getVnfPkgUri, null);
+               if (HttpStatus.SC_OK != rsp.getHttpStatusCode()) {
+                       LOGGER.error("Failed to download package from VNF Repository:uri={}, Response={}", uri, rsp);
+                       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+               }
+               LOGGER.debug(AuditMessages.AUDIT_MSG, "Response from VNF Repository for download package is success ");
+
+               // Step 3: Import the file to SDC and validate and send the response
+               try (InputStream fileStream = new BufferedInputStream(
+                               new ByteArrayInputStream(rsp.getResponse().getBytes(StandardCharsets.ISO_8859_1)))) {
+
+                       String filename = "temp_" + csarId + ".csar";
+                       OrchestrationTemplateCandidateManager candidateManager = OrchestrationTemplateCandidateManagerFactory
+                                       .getInstance().createInterface();
+                       UploadFileResponse uploadFileResponse = candidateManager.upload(vspId,
+                                       resolveVspVersion(vspId, null, user, VersionableEntityAction.Write), fileStream, user,
+                                       getFileExtension(filename), getNetworkPackageName(filename));
+
+                       UploadFileResponseDto uploadFileResponseDto = new MapUploadFileResponseToUploadFileResponseDto()
+                                       .applyMapping(uploadFileResponse, UploadFileResponseDto.class);
+
+                       return Response.ok(uploadFileResponseDto).build();
+               } catch (IOException e) {
+                       // Exception while uploading file
+                       LOGGER.error("Exception while uploading VNF package received from VNF Repository:", e);
+                       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+               }
+       }
+
+       @Override
+       public Response downloadVnfPackage(String vspId, String versionId, String csarId, String user) throws Exception {
+
+               LOGGER.audit(AuditMessages.AUDIT_MSG + "Download VNF Packages from Repository:csarId={}", csarId);
+
+               // Step 1: Create REST client and configuration and prepare URI
+               init();
+
+               // Step 2: Build URI based on the IP address and port allocated
+               String uri = String.format(downldPkgUri, csarId);
+               RestResponse rsp = iRestClnt.doGET(uri, null);
+               if (HttpStatus.SC_OK != rsp.getHttpStatusCode()) {
+                       LOGGER.error("Failed to download package from VNF Repository:uri={}, Response={}", uri, rsp);
+                       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+               }
+
+               // Step 3:Send response to the client
+               String filename = "temp_" + csarId + ".csar";
+               Response.ResponseBuilder response = Response.ok(rsp.getResponse().getBytes(StandardCharsets.ISO_8859_1));
+               response.header("Content-Disposition", "attachment; filename=" + filename);
+
+               LOGGER.debug(AuditMessages.AUDIT_MSG, "Response from VNF Repository for download package is success ");
+
+               return response.build();
+       }
+
+       private static void setRestClient() {
+
+               if (null == iRestClnt) {
+                       RestConfigurationInfo restInfo = new RestConfigurationInfo();
+                       iRestClnt = RestClientServiceFactory.createRestClientService(restInfo);
+                       if (null == iRestClnt) {
+                               return;
+                       }
+               }
+       }
+
+       private static void setVnfRepoConfig() {
+
+               try {
+                       // Step 1: Fetch the on-boarding configuration
+                       Configuration config = ConfigurationManager.lookup();
+
+                       String vnfRepoHost = config.getAsString(CONFIG_NAMESPACE, "vnfRepoHost");
+                       if (null != vnfRepoHost) {
+                               ipAddress = vnfRepoHost;
+                       }
+
+                       String vnfRepoPort = config.getAsString(CONFIG_NAMESPACE, "vnfRepoPort");
+                       if (null != vnfRepoPort) {
+                               port = vnfRepoPort;
+                       }
+
+                       String getVnfUri = config.getAsString(CONFIG_NAMESPACE, "getVnfUri");
+                       if (null != getVnfUri) {
+                               getVnfPkgUri = getVnfUri;
+                       }
+
+                       String downloadVnfUri = config.getAsString(CONFIG_NAMESPACE, "downloadVnfUri");
+                       if (null != downloadVnfUri) {
+                               downldPkgUri = downloadVnfUri;
+                       }
+
+               } catch (Exception e) {
+                       LOGGER.error("Failed to load configuration, Exception caught, using default configuration", e);
+               }
+
+               getVnfPkgUri = new StringBuilder("http://").append(ipAddress).append(":").append(port).append(getVnfPkgUri)
+                               .toString();
+
+               downldPkgUri = new StringBuilder("http://").append(ipAddress).append(":").append(port).append(downldPkgUri)
+                               .toString();
+       }
+
+       private static synchronized void init() throws Exception {
+               if (!initFlag) {
+                       // Step 1: Initialize configuration
+                       setVnfRepoConfig();
+
+                       // Step 2: Initialize rest client
+                       setRestClient();
+                       if (null == iRestClnt) {
+                               LOGGER.error("REST initialization error, Rest client is null");
+                               throw new Exception("Rest Initializer error, Rest client is null");
+                       }
+
+                       initFlag = true;
+               }
+       }
+}
diff --git a/openecomp-be/configuration/config-vnfsdk.yaml b/openecomp-be/configuration/config-vnfsdk.yaml
new file mode 100644 (file)
index 0000000..a59f671
--- /dev/null
@@ -0,0 +1,4 @@
+vnfRepoPort: 8702
+vnfRepoHost: 172.18.0.1
+getVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars
+downloadVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars/%s/files
index 7c726aa..3d7784c 100644 (file)
@@ -19,6 +19,7 @@
 @import "components/activityLog";
 @import "components/selectActionTable";
 @import "components/datepicker";
+@import "components/vnfBrowse";
 
 %noselect {
   -webkit-touch-callout: none;
diff --git a/openecomp-ui/resources/scss/components/_vnfBrowse.scss b/openecomp-ui/resources/scss/components/_vnfBrowse.scss
new file mode 100644 (file)
index 0000000..7e0085a
--- /dev/null
@@ -0,0 +1,109 @@
+$message-info-icon-size: 16px;
+
+.vnf-creation-page {
+       .list-editor-view-header {
+               border-bottom: none;
+       }
+       .vnfBrowse-list-item {
+               display: flex;
+               height: 36px;
+               @extend .body-1;
+               &.header {
+                       @extend .body-1-semibold;
+                       background-color: $tlv-light-gray;
+                       color: $text-black;
+               }
+               &.selectedRow {
+                       background-color: $blue;
+                       color: $white;
+                       .svg-icon-wrapper {
+                               &.__positive {
+                                       fill: $white;
+                                       color: $white;
+                               }
+                       }
+               }
+               .svg-icon-wrapper {
+                       &.__positive {
+                               fill: $dark-gray;
+                               color: $dark-gray;
+                       }
+               }
+       }
+
+       .activity-action {
+               .svg-icon-wrapper {
+                       float: left;
+               }
+       }
+
+       .message-further-info-icon {
+               background-color: $gray;
+       }
+
+       .table-cell {
+       border-right: 1px solid $light-gray;
+       border-bottom: 1px solid $light-gray;
+       &:last-child {
+               border-right: none;
+       }
+       flex-basis: 22%;
+       display: flex;
+       padding: 0 20px;
+       justify-content: center;
+       flex-direction: column;
+
+       &.vnftable-action {
+               flex-basis: 12%;
+               span {
+                       margin: auto;
+               }
+       }
+}
+
+  .vnf-table-header {
+       cursor: pointer;
+       display: flex;
+       align-items: center;
+       .header-sort-arrow {
+         width: 0;
+         height: 0;
+         border-left: 5px solid transparent;
+         border-right: 5px solid transparent;
+         margin-left: 9px;
+         &.up {
+               border-bottom: 5px solid $black;
+         }
+         &.down {
+               border-top: 5px solid $black;
+         }
+
+       }
+  }
+
+  .vnf-table-cell {
+       display: flex;
+       justify-content: space-between;
+       span {
+               overflow: hidden;
+               text-overflow: ellipsis;
+       }
+  } 
+  .vnftable-name {
+       max-width: 22%;
+  }
+
+  .vnf-grid-section {
+       margin: 20px 20px 20px 50px;
+  }
+
+  .vnf-modal {
+       text-align: right;
+       margin-top: 22px;
+  }
+  .vnf-submit {
+       margin-right: 15px;
+  }
+
+}
\ No newline at end of file
index 28b54cc..b674492 100644 (file)
           .or-text {
             margin-top: 10px;
             margin-bottom: 10px;
+            color: $light-gray;
           }
+          .upload {
+            width: 50%;
+          }
+          .vnfRepo {
+            width: 50%;
+            cursor: pointer;
+            .searchRepo-text {
+              color: $blue;
+              @extend .body-1-semibold;
+              width: 72px;
+              line-height: 24px;
+              margin-left: auto;
+              margin-right: auto;
+            }
+            .svg-icon-wrapper {
+              .svg-icon.__search {
+                width: 34px;
+                height: 34px;
+                margin-top: 10px;
+              }
+              &.__positive {
+                fill: $blue;
+                color: $blue;
+              }
+            }
+          }
+          .verticalLine {
+            height: 90%;
+            border-left: 1px solid $light-gray;
+          }
+        }
+        .showVnf {
+          flex-direction: row;
         }
       }
     }
index 629b944..5bea858 100644 (file)
  * or implied. See the License for the specific language governing
  * permissions and limitations under the License.
  */
-/**
- * The HTML structure here is aligned with bootstrap HTML structure for form elements.
- * In this way we have proper styling and it is aligned with other form elements on screen.
- *
- * Select and MultiSelect options:
- *
- * label - the label to be shown which paired with the input
- *
- * all other "react-select" props - as documented on
- * http://jedwatson.github.io/react-select/
- * or
- * https://github.com/JedWatson/react-select
- */
+
 import React, {Component} from 'react';
 import i18n from 'nfvo-utils/i18n/i18n.js';
 import Button from 'sdc-ui/lib/react/Button.js';
diff --git a/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
new file mode 100644 (file)
index 0000000..0673e6d
--- /dev/null
@@ -0,0 +1,60 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. 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.
+ */
+
+import React, {Component} from 'react';
+import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import Configuration from 'sdc-app/config/Configuration.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+
+function VNFBrowse({onBrowseVNF, isReadOnlyMode}) {
+       if(!Configuration.get('showBrowseVNF')) {
+               return (
+                       <div/>
+               );
+       }
+       else {
+               return (
+                       <div className={`${'vnfRepo'}${isReadOnlyMode ? ' disabled' : ''}`} onClick={onBrowseVNF}>
+                               <div className={`${'searchRepo-text'}`}>{i18n('Search in Repository')}</div>
+                   <SVGIcon name='search' color='positive' iconClassName='searchIcon'/>
+                       </div>
+               );
+       }
+}
+
+class VnfRepositorySearchBox extends Component {
+       render() {
+               let {className, onClick, onBrowseVNF, dataTestId, isReadOnlyMode} = this.props;
+               let showVNF = Configuration.get('showBrowseVNF');
+               return (
+                       <div
+                               className={`${className}${isReadOnlyMode ? ' disabled' : ''}`}>
+                               <DraggableUploadFileBox
+                                       dataTestId={dataTestId}
+                                       isReadOnlyMode={isReadOnlyMode}
+                                       className={'upload'}
+                                       onClick={onClick}/>
+
+                               <div className={`${'verticalLine'}${showVNF ? '' : ' hide'}`}></div>
+
+                               <VNFBrowse onBrowseVNF={onBrowseVNF} isReadOnlyMode={isReadOnlyMode}/>
+                       </div>
+               );
+               
+       }
+}
+export default VnfRepositorySearchBox;
index c878c9e..648c159 100644 (file)
@@ -55,6 +55,10 @@ class RestAPIUtil extends RestfulAPI {
        applySecurity(options, data) {
                let headers = options.headers || (options.headers = {});
 
+               if (options.isAnonymous) {
+                       return;
+               }
+
                let authToken = localStorage.getItem(STORAGE_AUTH_KEY);
                if (authToken) {
                        headers[AUTHORIZATION_HEADER] = authToken;
index 8ed6383..5cb2a53 100644 (file)
   "Persistent Storage/Volume Size (GB)": "Persistent Storage/Volume Size (GB)",
   "I/O Operations (per second)": "I/O Operations (per second)",
   "CPU Oversubscription Ratio": "CPU Oversubscription Ratio",
-  "Memory - RAM": "Memory - RAM"
+  "Memory - RAM": "Memory - RAM",
+  "VNF List Title": "VNF List",
+  "VNF import failed title" : "VNF import failed",
+  "VNF import failed msg" : "VNF Repository Server is not responding or not reachable. Please check server address in configuration file.",
+  "VNF Header Name" : "Name",
+  "VNF Header Version" : "Version",
+  "VNF Header Vendor" : "Vendor",
+  "VNF Header Desc" : "Description",
+  "VNF Header Action" : "Action"
 }
index 8c10beb..3d03d00 100644 (file)
@@ -23,6 +23,7 @@ import NICCreation from 'sdc-app/onboarding/softwareProduct/components/network/N
 import SoftwareProductComponentsNICEditor from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js';
 import ComponentCreation from 'sdc-app/onboarding/softwareProduct/components/creation/SoftwareProductComponentCreation.js';
 import SoftwareProductDeploymentEditor from 'sdc-app/onboarding/softwareProduct/deployment/editor/SoftwareProductDeploymentEditor.js';
+import VNFImport from 'sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js';
 
 export const modalContentMapper = {
        SOFTWARE_PRODUCT_CREATION: 'SOFTWARE_PRODUCT_CREATION',
@@ -33,7 +34,8 @@ export const modalContentMapper = {
        NIC_CREATION: 'NIC_CREATION',
        COMPONENT_CREATION: 'COMPONENT_CREATION',
        SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR : 'SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR',
-       DEPLOYMENT_FLAVOR_EDITOR: 'DEPLOYMENT_FLAVOR_EDITOR'
+       DEPLOYMENT_FLAVOR_EDITOR: 'DEPLOYMENT_FLAVOR_EDITOR',
+       VNF_IMPORT: 'VNF_IMPORT'
 };
 
 export const modalContentComponents = {
@@ -45,5 +47,6 @@ export const modalContentComponents = {
        NIC_CREATION: NICCreation,
        COMPONENT_CREATION: ComponentCreation,
        SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR : SoftwareProductComponentImageEditor,
-       DEPLOYMENT_FLAVOR_EDITOR: SoftwareProductDeploymentEditor
+       DEPLOYMENT_FLAVOR_EDITOR: SoftwareProductDeploymentEditor,
+       VNF_IMPORT: VNFImport
 };
index 2725cf1..361088b 100644 (file)
@@ -4,5 +4,6 @@
        "build": "dev",
        "appContextPath" : "/onboarding",
        "defaultRestPrefix": "/onboarding-api",
-       "defaultRestATTPrefix": "/sdc1/feProxy/rest"
+       "defaultRestATTPrefix": "/sdc1/feProxy/rest",
+       "showBrowseVNF" : true
 }
index 41306a1..aa41818 100644 (file)
@@ -48,6 +48,10 @@ function uploadFile(vspId, formData, version) {
 
 }
 
+function uploadVNFFile(csarId, softwareProductId, version) {
+       return RestAPIUtil.post(`${baseUrl()}${softwareProductId}/versions/${version.id}/vnfrepository/vnfpackage/${csarId}/import`);
+}
+
 function putSoftwareProduct(softwareProduct) {
        return RestAPIUtil.put(`${baseUrl()}${softwareProduct.id}/versions/${softwareProduct.version.id}`, {
                name: softwareProduct.name,
@@ -298,6 +302,45 @@ const SoftwareProductActionHelper = {
                        });
        },
 
+       uploadVNFFile(dispatch, {csarId, failedNotificationTitle, softwareProductId, version}) {
+               dispatch({
+                       type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
+                       payload: {}
+               });
+
+               Promise.resolve()
+                       .then(() => uploadVNFFile(csarId, softwareProductId, version))
+                       .then(response => {
+                               if (response.status === 'Success') {
+                                       dispatch({
+                                               type: commonActionTypes.DATA_CHANGED,
+                                               deltaData: {onboardingOrigin: response.onboardingOrigin},
+                                               formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
+                                       });
+                                       switch(response.onboardingOrigin){
+                                               case onboardingOriginTypes.ZIP:
+                                                       OnboardingActionHelper.navigateToSoftwareProductAttachmentsSetupTab(dispatch, {softwareProductId, version});
+                                                       break;
+                                               case onboardingOriginTypes.CSAR:
+                                                       OnboardingActionHelper.navigateToSoftwareProductAttachmentsValidationTab(dispatch, {softwareProductId, version});
+                                                       break;
+                                       }
+                               }
+                               else {
+                                       throw new Error(parseUploadErrorMsg(response.errors));
+                               }
+                       })
+                       .catch(error => {
+                               dispatch({
+                                       type: modalActionTypes.GLOBAL_MODAL_ERROR,
+                                       data: {
+                                               title: failedNotificationTitle,
+                                               msg: error.message
+                                       }
+                               });
+                       });
+       },
+
        downloadHeatFile(dispatch, {softwareProductId, heatCandidate, isReadOnlyMode, version}){
                let p = isReadOnlyMode ? Promise.resolve() : SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate, version});
                p.then(() => {
index d7a6c2e..c9159f1 100644 (file)
@@ -47,6 +47,8 @@ import {COMPONENTS_QUESTIONNAIRE, COMPONENTS_COMPUTE_QUESTIONNAIRE} from 'sdc-ap
 import {NIC_QUESTIONNAIRE} from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js';
 import {IMAGE_QUESTIONNAIRE} from 'sdc-app/onboarding/softwareProduct/components/images/SoftwareProductComponentsImageConstants.js';
 
+import VNFImportReducer from './vnfMarketPlace/VNFImportReducer.js';
+
 export default combineReducers({
        softwareProductAttachments: combineReducers({
                attachmentsDetails: SoftwareProductAttachmentsReducer,
@@ -98,5 +100,6 @@ export default combineReducers({
                }
                return state;
        },
-       softwareProductQuestionnaire: createJSONSchemaReducer(PRODUCT_QUESTIONNAIRE)
+       softwareProductQuestionnaire: createJSONSchemaReducer(PRODUCT_QUESTIONNAIRE),
+       VNFMarketPlaceImport: VNFImportReducer
 });
index a13e742..65beebf 100644 (file)
@@ -21,6 +21,7 @@ import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/Soft
 import LandingPageView from './SoftwareProductLandingPageView.jsx';
 import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js';
 import {onboardingMethod} from '../SoftwareProductConstants.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
 
 export const mapStateToProps = ({softwareProduct, licenseModel: {licenseAgreement}}) => {
        let {softwareProductEditor: {data:currentSoftwareProduct = {}}, softwareProductComponents, softwareProductCategories = []} = softwareProduct;
@@ -104,7 +105,11 @@ const mapActionsToProps = (dispatch, {version}) => {
                        OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version });
                },
                /** for the next version */
-               onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch)
+               onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch),
+
+               onBrowseVNF: (currentSoftwareProduct) => {
+                       VNFImportActionHelper.open(dispatch, currentSoftwareProduct);
+               }
        };
 };
 
index 72a4164..e18860c 100644 (file)
@@ -19,7 +19,9 @@ import Dropzone from 'react-dropzone';
 
 
 import i18n from 'nfvo-utils/i18n/i18n.js';
+import Configuration from 'sdc-app/config/Configuration.js';
 import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import VnfRepositorySearchBox from 'nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx';
 
 import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
 import SoftwareProductComponentsList from '../components/SoftwareProductComponentsList.js';
@@ -104,19 +106,38 @@ class SoftwareProductLandingPageView extends React.Component {
        }
 
        renderProductDetails(isManual, isReadOnlyMode) {
-               return (
-                       <div className='details-panel'>
-                               { !isManual && <div>
-                                       <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
-                                               <DraggableUploadFileBox
-                                                       dataTestId='upload-btn'
-                                                       isReadOnlyMode={isReadOnlyMode}
-                                                       className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}
-                                                       onClick={() => this.refs.fileInput.open()}/>
-                                       </div>
-                               }
-                       </div>
-               );
+               let {onBrowseVNF, currentSoftwareProduct} = this.props;
+
+               if(Configuration.get('showBrowseVNF')) {
+                       return (
+                               <div className='details-panel'>
+                                       { !isManual && <div>
+                                               <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
+                                                       <VnfRepositorySearchBox
+                                                               dataTestId='upload-btn'
+                                                               isReadOnlyMode={isReadOnlyMode}
+                                                               className={classnames('software-product-landing-view-top-block-col-upl showVnf', {'disabled': isReadOnlyMode})}
+                                                               onClick={() => this.refs.fileInput.open()} onBrowseVNF={() => onBrowseVNF(currentSoftwareProduct)}/>
+                                               </div>
+                                       }
+                               </div>
+                       );
+               }
+               else {
+                       return (
+                               <div className='details-panel'>
+                                       { !isManual && <div>
+                                               <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
+                                                       <DraggableUploadFileBox
+                                                               dataTestId='upload-btn'
+                                                               isReadOnlyMode={isReadOnlyMode}
+                                                               className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}
+                                                               onClick={() => this.refs.fileInput.open()} onBrowseVNF={() => onBrowseVNF()}/>
+                                               </div>
+                                       }
+                               </div>
+                       );
+               }       
        }
 
        handleImportSubmit(files, isReadOnlyMode, isManual) {
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js
new file mode 100644 (file)
index 0000000..3fb0bfd
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import {connect} from 'react-redux';
+import VNFImportView from './VNFImportView.jsx';
+import VNFImportActionHelper  from './VNFImportActionHelper.js';
+
+export const mapStateToProps = (response) => {
+       const { softwareProduct: { VNFMarketPlaceImport : { vnfItems } } } = response;
+       return {
+               vnfItems: vnfItems
+       };
+};
+
+export const mapActionsToProps = (dispatch) => {
+       return {
+               onCancel: () => VNFImportActionHelper.resetData(dispatch),
+               onSubmit: (csarId, selectedVendor) => {
+                       VNFImportActionHelper.uploadData(selectedVendor, csarId, dispatch);
+               }
+       };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(VNFImportView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js
new file mode 100644 (file)
index 0000000..f8cec1c
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as modalActionTypes, modalSizes} from 'nfvo-components/modal/GlobalModalConstants.js';
+import {modalContentMapper} from 'sdc-app/common/modal/ModalContentMapper.js';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import {actionTypes} from './VNFImportConstants.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function baseUrl(selectedVendor) {
+       const restPrefix = Configuration.get('restPrefix');
+       let vspId = selectedVendor.id;
+       let version = selectedVendor.version;
+       return `${restPrefix}/v1.0/vendor-software-products/${vspId}/versions/${version.id}/vnfrepository`;
+}
+
+function getVNFMarketplace(dispatch, currentSoftwareProduct) {
+       return RestAPIUtil.fetch(`${baseUrl(currentSoftwareProduct)}/vnfpackages`, {
+               isAnonymous: false
+       })
+       .then((response) => {
+               dispatch({
+                       type: actionTypes.OPEN,
+                       response
+               });
+               dispatch({
+                       type: modalActionTypes.GLOBAL_MODAL_SHOW,
+                       data: {
+                               modalComponentName: modalContentMapper.VNF_IMPORT,
+                               title: i18n('Browse VNF'),                              
+                               modalComponentProps: {
+                                       currentSoftwareProduct,
+                                       size: modalSizes.LARGE                                  
+                               }
+                       }
+               });
+       })
+       .catch((error) => {
+               let errMessage = error.responseJSON ? error.responseJSON.message : i18n('VNF import failed msg');
+               
+               dispatch({                                      
+                       type: modalActionTypes.GLOBAL_MODAL_ERROR,
+                       data: {
+                               title: i18n('VNF import failed title'),
+                               msg: errMessage,
+                               cancelButtonText: i18n('Ok')
+                       }
+               });
+       });
+}
+
+function downloadCSARFile(csarId, currSoftwareProduct) {
+       let url = `${baseUrl(currSoftwareProduct)}/vnfpackage/${csarId}/download`;
+       return RestAPIUtil.fetch(url, {
+               dataType: 'binary',
+               isAnonymous: false
+       });
+}
+
+function getFileName(xhr, defaultFilename) {
+       let filename = '';
+       let contentDisposition = xhr.getResponseHeader('Content-Disposition') ? xhr.getResponseHeader('Content-Disposition') : '';
+       let match = contentDisposition.match(/filename=(.*?)(;|$)/);
+       if (match) {
+               filename = match[1].replace(/['"]/g, '');;
+       }
+       else {
+               filename = defaultFilename;
+       }
+       return filename;
+}
+
+function uploadVNFData(csarId, currSoftwareProduct, dispatch) {
+
+       let softwareProductId = currSoftwareProduct.id;
+       let version = currSoftwareProduct.version;
+
+       SoftwareProductActionHelper.uploadVNFFile(dispatch, {
+               csarId,
+               currSoftwareProduct,    
+               failedNotificationTitle: i18n('Upload validation failed'),
+               softwareProductId,
+               version
+       });
+}
+
+function getTimestampString() {
+       let date = new Date();
+       let z = n => n < 10 ? '0' + n : n;
+       return `${date.getFullYear()}-${z(date.getMonth())}-${z(date.getDate())}_${z(date.getHours())}-${z(date.getMinutes())}`;
+}
+
+function showFileSaveDialog({blob, xhr, defaultFilename, addTimestamp}) {
+       let filename = getFileName(xhr, defaultFilename);
+
+       if (addTimestamp) {
+               filename = filename.replace(/(^.*?)\.([^.]+$)/, `$1_${getTimestampString()}.$2`);
+       }
+
+       let link = document.createElement('a');
+       let url = URL.createObjectURL(blob);
+       link.href = url;
+       link.download = filename;
+       link.style.display = 'none';
+       document.body.appendChild(link);
+       link.click();
+       setTimeout(function(){
+               document.body.removeChild(link);
+               URL.revokeObjectURL(url);
+       }, 0);
+}
+
+const VNFImportActionHelper = {
+
+       open(dispatch, currentSoftwareProduct) {
+               getVNFMarketplace(dispatch, currentSoftwareProduct);
+       },
+
+       download(csarId, currSoftwareProduct) {
+               downloadCSARFile(csarId, currSoftwareProduct)
+                       .then((blob, statusText, xhr) => showFileSaveDialog({blob, xhr, defaultFilename: 'MyNewCSAR.csar', addTimestamp: true}));
+       },
+
+       resetData(dispatch) {
+
+               dispatch({
+                       type: modalActionTypes.GLOBAL_MODAL_CLOSE
+               });
+
+               dispatch({
+                       type: actionTypes.RESET_DATA
+               });
+       },
+
+       getVNFMarketplace(dispatch) {
+               return getVNFMarketplace(dispatch);
+       },
+
+       uploadData(currSoftwareProduct, csarId, dispatch) {
+               this.resetData(dispatch);
+               uploadVNFData(csarId, currSoftwareProduct, dispatch);
+       }
+};
+
+export default VNFImportActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js
new file mode 100644 (file)
index 0000000..763cf94
--- /dev/null
@@ -0,0 +1,22 @@
+
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+       OPEN: null,
+       RESET_DATA: null
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js
new file mode 100644 (file)
index 0000000..4149a73
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import {actionTypes} from './VNFImportConstants.js';
+
+
+export default (state = {}, action) => {
+       switch (action.type) {
+               case actionTypes.OPEN:
+                       return {
+                               ...state,
+                               showModal: true,
+                               vnfItems: action.response,
+                       };
+               case actionTypes.RESET_DATA:
+                       return {};
+               default:
+                       return state;
+       }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx
new file mode 100644 (file)
index 0000000..f56396f
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import React from 'react';
+import GridSection from 'nfvo-components/grid/GridSection.jsx';
+import GridItem from 'nfvo-components/grid/GridItem.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+import Button from 'sdc-ui/lib/react/Button.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
+
+
+function VNFAction({action, isHeader, downloadCSAR, id, currSoftwareProduct}) {
+       if (isHeader) {
+               return <span>{action}</span>;
+       }
+       return (
+               <span>
+                       <SVGIcon name='download' color='positive' onClick={() => {downloadCSAR(id, currSoftwareProduct);}}/>
+               </span>
+       );
+}
+
+function VNFSortableCellHeader({isHeader, data, isDes, onSort, activeSortColumn}) {
+       //TODO check icon sdc-ui
+       if (isHeader) {
+               if(activeSortColumn === data) {
+                       return (
+                               <span className='vnf-table-header' onClick={()=>{onSort(activeSortColumn);}}>
+                                       <span>{data}</span>
+                                       <span className={`header-sort-arrow ${isDes ? 'up' : 'down'}`}></span>
+                               </span>
+                       );
+               }
+               else {
+                       return (
+                               <span className='vnf-table-header' onClick={()=>{activeSortColumn = data; onSort(activeSortColumn);}}>
+                                       <span>{data}</span>
+                               </span>
+                       );
+               }
+       }
+       return (
+               <span className='vnf-table-cell'>
+                       <span>{data}</span>
+               </span>
+       );
+}
+
+export function VNFItemList({vnf, isHeader, isDes, onSort, activeSortColumn, downloadCSAR, selectTableRow, selectedRow, currentSoftwareProduct}) {
+       let {csarId, name, version, provider, shortDesc, action} = vnf;
+       return (
+               <li className={`vnfBrowse-list-item ${isHeader ? 'header' : ''} ${csarId === selectedRow ? 'selectedRow' : ''}`} 
+                       data-test-id='vnfBrowse-list-item' onClick={() => {selectTableRow(csarId);}}>
+                       <div className='table-cell vnftable-name' data-test-id='vnftable-name'>
+                               <VNFSortableCellHeader isHeader={isHeader} data={name} isDes={isDes} onSort={(activeSort)=> {onSort('name', activeSort);}} activeSortColumn={activeSortColumn}/>
+                       </div>
+                       <div className='table-cell vnftable-version' data-test-id='vnftable-version'>
+                               <VNFSortableCellHeader isHeader={isHeader} data={version} isDes={isDes} onSort={(activeSort)=> {onSort('version', activeSort);}} activeSortColumn={activeSortColumn}/>
+                       </div>
+                       <div className='table-cell vnftable-provider' data-test-id='vnftable-provider'>
+                               <VNFSortableCellHeader isHeader={isHeader} data={provider} isDes={isDes} onSort={(activeSort)=> {onSort('provider', activeSort);}} activeSortColumn={activeSortColumn}/>
+                       </div>
+                       <div className='table-cell vnftable-shortDesc' data-test-id='vnftable-shortDesc'>
+                               <VNFSortableCellHeader isHeader={isHeader} data={shortDesc} isDes={isDes} onSort={(activeSort)=> {onSort('shortDesc', activeSort);}} activeSortColumn={activeSortColumn}/>
+                       </div>
+                       <div className='table-cell vnftable-action' data-test-id='vnftable-action'>
+                               <VNFAction isHeader={isHeader} action={action} downloadCSAR= {downloadCSAR} id={csarId} currSoftwareProduct={currentSoftwareProduct}/>
+                       </div>
+               </li>
+       );
+}
+
+class VNFImportView extends React.Component {
+
+       state = {
+               localFilter: '',
+               sortDescending: true,
+               sortCrit: 'name',
+               activeSortColumn : 'Name',
+               selectedRow: ''
+       };
+
+       render() {
+               let {onCancel, onSubmit, currentSoftwareProduct} = this.props;
+               
+               return (
+                       <div className='vnf-creation-page'>
+                                       <GridSection className='vnf-grid-section'>
+                                               <GridItem colSpan='4'>
+                                                       <ListEditorView
+                                                               title={i18n('VNF List Title')}
+                                                               filterValue={this.state.localFilter}
+                                                               onFilter={filter => this.setState({localFilter: filter})}>
+                                                               <VNFItemList
+                                                                       isHeader={true}
+                                                                       vnf={{csarId: 0, name: i18n('VNF Header Name'), version: i18n('VNF Header Version'), 
+                                                                               provider: i18n('VNF Header Vendor'), shortDesc: i18n('VNF Header Desc'), action: i18n('VNF Header Action')}}
+                                                                       isDes={this.state.sortDescending}
+                                                                       onSort={(sortCriteria, activeSortCol) => this.setState({sortDescending: !this.state.sortDescending, sortCrit: sortCriteria, activeSortColumn: activeSortCol})} 
+                                                                       activeSortColumn={this.state.activeSortColumn}/>
+                                                               {this.sortVNFItems(this.filterVNFItems(), 
+                                                                       this.state.sortDescending, this.state.sortCrit).map(vnf => <VNFItemList key={vnf.id} vnf={vnf} 
+                                                                               downloadCSAR={this.downloadCSAR} selectTableRow={(selID) => 
+                                                                                       {this.setState({selectedRow: selID});this.selectTableRow(selID);}} selectedRow={this.state.selectedRow} currentSoftwareProduct={currentSoftwareProduct}/>)}
+                                                       </ListEditorView>
+                                               </GridItem>
+                                               <GridItem colSpan='4'>
+                                                       <div className='vnf-modal'>
+                                               <Button className='vnf-submit' type='button' btnType='default' onClick={() => 
+                                                       onSubmit(this.state.selectedRow, currentSoftwareProduct)}>{i18n('OK')}
+                                               </Button>
+                                               <Button className='Cancel' type='button' btnType='outline' onClick={onCancel}>{i18n('Cancel')}
+                                               </Button>
+                                                       </div>
+                                               </GridItem>
+                                       </GridSection>
+                       </div>
+               );
+       }
+
+       filterVNFItems() {
+               let {vnfItems} = this.props;
+               let {localFilter} = this.state;
+               if (localFilter.trim()) {
+                       const filter = new RegExp(escape(localFilter), 'i');
+                       return vnfItems.filter(
+                               ({name = '', provider = '', version = '', shortDesc = ''}) => 
+                                       escape(name).match(filter) || escape(provider).match(filter) || escape(version).match(filter) || escape(shortDesc).match(filter));
+               }
+               else {
+                       return vnfItems;
+               }
+       }
+
+       sortVNFItems(vnfItems, sortDesc, sortCrit) {
+               if (sortDesc) {
+                       return vnfItems.sort((a, b) => 
+                       {
+                               if(a[sortCrit] < b[sortCrit]){
+                                       return -1;
+                               } 
+                               else if(a[sortCrit] > b[sortCrit]){
+                                       return 1;
+                               } 
+                               else {
+                                       return 0;
+                               }
+                       });
+               }
+               else {
+                       return vnfItems.reverse();
+               }
+       }
+
+       downloadCSAR(id, currSoftwareProduct) {
+               VNFImportActionHelper.download(id, currSoftwareProduct);
+       }
+       
+}
+
+export default VNFImportView;
index 1022a03..267579e 100644 (file)
             "BE": "yyy",
             "FE": "yyy",
             "ES": "yyy"
+        },
+        "VnfRepo": {
+            "vnfRepoPort": "8702",
+            "vnfRepoHost": "yyy"
         }
     },
     "override_attributes": {
index 04c2a93..ad40309 100644 (file)
                                                                                        <include>Artifact-Generator.properties</include>
                                                                                        <include>error-configuration.yaml</include>
                                                                                        <include>ecomp-error-configuration.yaml</include>
+                                                                                        <include>config-vnfsdk.yaml</include>
                                                                                        <include>logback.xml</include>
                                                                                </includes>
                                                                        </resource>
index d989eb2..bd653a7 100644 (file)
@@ -46,3 +46,12 @@ directory "BE_create_catalog-be" do
   mode '0755'
   action :create
 end
+
+directory "BE_create_onboarding-be" do
+  path "/var/lib/jetty/config/onboarding-be"
+  owner 'jetty'
+  group 'jetty'
+  mode '0755'
+  action :create
+end
+
index 067642f..e9a00c3 100644 (file)
@@ -51,3 +51,16 @@ cookbook_file "ArtifactGenerator" do
    group "jetty"
    mode "0755"
 end
+
+
+template "VnfrepoConfiguration" do
+   path "/#{jetty_base}/config/onboarding-be/config-vnfsdk.yaml"
+   source "BE-vnfrepo-configuration.yaml.erb"
+   owner "jetty"
+   group "jetty"
+   mode "0755"
+   variables({
+      :VNFREPO_IP   => node['VnfRepo']['vnfRepoHost'],
+      :VNFREPO_PORT => node['VnfRepo']['vnfRepoPort']
+   })
+end
\ No newline at end of file
diff --git a/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/BE-vnfrepo-configuration.yaml.erb b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/BE-vnfrepo-configuration.yaml.erb
new file mode 100644 (file)
index 0000000..07e2662
--- /dev/null
@@ -0,0 +1,4 @@
+vnfRepoPort: <%= @VNFREPO_PORT %>
+vnfRepoHost: <%= @VNFREPO_IP %>
+getVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars
+downloadVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars/%s/files
\ No newline at end of file
index 8e5926c..4f3d082 100644 (file)
@@ -6,7 +6,7 @@ cd /root/chef-solo
 echo "normal['HOST_IP'] = \"${HOST_IP}\"" > /root/chef-solo/cookbooks/sdc-catalog-be/attributes/default.rb
 chef-solo -c solo.rb -E ${CHEFNAME}
 
-sed -i '/^set -e/aJAVA_OPTIONS=\"-Xdebug -agentlib:jdwp=transport=dt_socket,address=4000,server=y,suspend=n -XX:MaxPermSize=256m -Xmx1500m -Dconfig.home=${JETTY_BASE}\/config -Dlog.home=${JETTY_BASE}\/logs -Dlogback.configurationFile=${JETTY_BASE}\/config\/catalog-be\/logback.xml -Dconfiguration.yaml=${JETTY_BASE}\/config\/catalog-be\/configuration.yaml -Dartifactgenerator.config=${JETTY_BASE}\/config\/catalog-be\/Artifact-Generator.properties\ -Donboarding_configuration.yaml=${JETTY_BASE}\/config\/onboarding-be\/onboarding_configuration.yaml" ' /docker-entrypoint.sh
+sed -i '/^set -e/aJAVA_OPTIONS=\"-Xdebug -agentlib:jdwp=transport=dt_socket,address=4000,server=y,suspend=n -XX:MaxPermSize=256m -Xmx1500m -Dconfig.home=${JETTY_BASE}\/config -Dlog.home=${JETTY_BASE}\/logs -Dlogback.configurationFile=${JETTY_BASE}\/config\/catalog-be\/logback.xml -Dconfiguration.yaml=${JETTY_BASE}\/config\/catalog-be\/configuration.yaml -Dartifactgenerator.config=${JETTY_BASE}\/config\/catalog-be\/Artifact-Generator.properties\ -Donboarding_configuration.yaml=${JETTY_BASE}\/config\/onboarding-be\/onboarding_configuration.yaml -Dconfig.location=${JETTY_BASE}\/config\/onboarding-be\/." ' /docker-entrypoint.sh
 sed -i '/^set -e/aTMPDIR=${JETTY_BASE}\/temp' /docker-entrypoint.sh
 
 # executiong the jetty