diff options
author | Murali-P <murali.p@huawei.com> | 2017-10-16 15:07:16 +0530 |
---|---|---|
committer | Murali-P <murali.p@huawei.com> | 2017-12-04 11:04:54 +0530 |
commit | baebe989f102c10d5077fb24a6948893284a236d (patch) | |
tree | 84566a75879428a950f04a9c369ab1a80afcd8f7 | |
parent | dcf55799473fe5d585ff1bc8121ab48485baab2b (diff) |
Integrate VNF Repository with SDC
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, 1189 insertions, 33 deletions
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml index 9808d76a9c..ee1b9b3d65 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml @@ -32,6 +32,11 @@ </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> <version>${project.version}</version> </dependency> diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml index 126f7814d7..fb2db2b362 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml @@ -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"/> diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml index eb7fe70691..e311a7ca1e 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml @@ -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 index 0000000000..47ed01d2d9 --- /dev/null +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml @@ -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 index 0000000000..1d922a63e8 --- /dev/null +++ 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 @@ -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 index 0000000000..acbfb328f1 --- /dev/null +++ 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 @@ -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 index 0000000000..a59f6711b9 --- /dev/null +++ b/openecomp-be/configuration/config-vnfsdk.yaml @@ -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 diff --git a/openecomp-ui/resources/scss/_components.scss b/openecomp-ui/resources/scss/_components.scss index 7c726aa8dd..3d7784ca8d 100644 --- a/openecomp-ui/resources/scss/_components.scss +++ b/openecomp-ui/resources/scss/_components.scss @@ -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 index 0000000000..7e0085af8a --- /dev/null +++ b/openecomp-ui/resources/scss/components/_vnfBrowse.scss @@ -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 diff --git a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss index 28b54cc78f..b674492047 100644 --- a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss +++ b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss @@ -187,7 +187,41 @@ .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; } } } diff --git a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx index 629b9449a2..5bea858ab7 100644 --- a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx +++ b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx @@ -13,19 +13,7 @@ * 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 index 0000000000..0673e6d698 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx @@ -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; diff --git a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js index c878c9e673..648c159908 100644 --- a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js +++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js @@ -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; diff --git a/openecomp-ui/src/nfvo-utils/i18n/en.json b/openecomp-ui/src/nfvo-utils/i18n/en.json index 8ed638352c..5cb2a536a4 100644 --- a/openecomp-ui/src/nfvo-utils/i18n/en.json +++ b/openecomp-ui/src/nfvo-utils/i18n/en.json @@ -326,5 +326,13 @@ "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" } diff --git a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js index 8c10beb952..3d03d00b09 100644 --- a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js +++ b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js @@ -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 }; diff --git a/openecomp-ui/src/sdc-app/config/config.json b/openecomp-ui/src/sdc-app/config/config.json index 2725cf1310..361088b3dd 100644 --- a/openecomp-ui/src/sdc-app/config/config.json +++ b/openecomp-ui/src/sdc-app/config/config.json @@ -4,5 +4,6 @@ "build": "dev", "appContextPath" : "/onboarding", "defaultRestPrefix": "/onboarding-api", - "defaultRestATTPrefix": "/sdc1/feProxy/rest" + "defaultRestATTPrefix": "/sdc1/feProxy/rest", + "showBrowseVNF" : true } diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js index 41306a1c1a..aa41818b97 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js @@ -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(() => { diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js index d7a6c2ef5c..c9159f192e 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js @@ -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 }); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js index a13e742006..65beebf106 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js @@ -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); + } }; }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx index 72a416473c..e18860c4f5 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx @@ -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 index 0000000000..3fb0bfd637 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js @@ -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 index 0000000000..f8cec1c1cb --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js @@ -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 index 0000000000..763cf94ec7 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js @@ -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 index 0000000000..4149a737c1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js @@ -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 index 0000000000..f56396f48d --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx @@ -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; diff --git a/sdc-os-chef/environments/Template.json b/sdc-os-chef/environments/Template.json index 1022a03285..267579ec08 100644 --- a/sdc-os-chef/environments/Template.json +++ b/sdc-os-chef/environments/Template.json @@ -36,6 +36,10 @@ "BE": "yyy", "FE": "yyy", "ES": "yyy" + }, + "VnfRepo": { + "vnfRepoPort": "8702", + "vnfRepoHost": "yyy" } }, "override_attributes": { diff --git a/sdc-os-chef/pom.xml b/sdc-os-chef/pom.xml index 04c2a9355f..ad40309455 100644 --- a/sdc-os-chef/pom.xml +++ b/sdc-os-chef/pom.xml @@ -182,6 +182,7 @@ <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> diff --git a/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_1_cleanup_jettydir.rb b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_1_cleanup_jettydir.rb index d989eb20bb..bd653a7ec5 100644 --- a/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_1_cleanup_jettydir.rb +++ b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_1_cleanup_jettydir.rb @@ -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 + diff --git a/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_2_setup_configuration.rb b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_2_setup_configuration.rb index 067642fed9..e9a00c3d95 100644 --- a/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_2_setup_configuration.rb +++ b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_2_setup_configuration.rb @@ -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 index 0000000000..07e26629f0 --- /dev/null +++ b/sdc-os-chef/sdc-backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/BE-vnfrepo-configuration.yaml.erb @@ -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 diff --git a/sdc-os-chef/sdc-backend/startup.sh b/sdc-os-chef/sdc-backend/startup.sh index 8e5926cdc4..4f3d0823d4 100644 --- a/sdc-os-chef/sdc-backend/startup.sh +++ b/sdc-os-chef/sdc-backend/startup.sh @@ -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 |