Implement CBA upload through REST 36/75836/7
authorAlexis de Talhouët <adetalhouet89@gmail.com>
Tue, 15 Jan 2019 19:44:58 +0000 (14:44 -0500)
committerAlexis de Talhouët <adetalhouet89@gmail.com>
Fri, 18 Jan 2019 18:56:01 +0000 (13:56 -0500)
Change-Id: I417254c5107f8b0031932e6a7cf0599561ee9a3c
Issue-ID: CCSDK-910
Signed-off-by: Alexis de Talhouët <adetalhouet89@gmail.com>
components/core/src/main/kotlin/org/onap/ccsdk/apps/controllerblueprints/core/utils/BluePrintArchiveUtils.kt
ms/blueprintsprocessor/functions/netconf-executor/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/functions/netconf/executor/ComponentNetconfExecutorTest.kt
ms/blueprintsprocessor/functions/python-executor/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/functions/python/executor/ComponentJythonExecutorTest.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/BluePrintProcessingGRPCHandler.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/ExecutionServiceController.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/ExecutionServiceHandler.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/utils/Utils.kt [moved from ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/utils/TimeUtils.kt with 51% similarity]
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/ExecutionServiceHandlerTest.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/mock/Mock.kt [new file with mode: 0644]
ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/services/execution/AbstractComponentFunction.kt

index ab5175d..fe7929e 100755 (executable)
@@ -24,13 +24,18 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
 import org.apache.commons.io.IOUtils
 import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintException
 import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintProcessorException
-import java.io.*
+import org.slf4j.LoggerFactory
+import java.io.BufferedInputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.IOException
 import java.nio.charset.Charset
 import java.util.zip.ZipFile
 
 class BluePrintArchiveUtils {
 
     companion object {
+        private val log = LoggerFactory.getLogger(BluePrintArchiveUtils::class.java)
 
         fun getFileContent(fileName: String): String = runBlocking {
             async {
@@ -51,50 +56,20 @@ class BluePrintArchiveUtils {
         /**
          * Create a new Zip from a root directory
          *
-         * @param directory the base directory
-         * @param filename the output filename
+         * @param source the base directory
+         * @param destination the output filename
          * @param absolute store absolute filepath (from directory) or only filename
          * @return True if OK
          */
         fun compress(source: File, destination: File, absolute: Boolean): Boolean {
-            // recursive call
-            val zaos: ZipArchiveOutputStream
             try {
-                zaos = ZipArchiveOutputStream(FileOutputStream(destination))
-            } catch (e: FileNotFoundException) {
-                return false
-            }
-
-            try {
-                recurseFiles(source, source, zaos, absolute)
-            } catch (e2: IOException) {
-                try {
-                    zaos.close()
-                } catch (e: IOException) {
-                    // ignore
+                ZipArchiveOutputStream(destination).use {
+                    recurseFiles(source, source, it, absolute)
                 }
-
+            } catch (e: Exception) {
+                log.error("Fail to compress folder(:$source) to path(${destination.path}", e)
                 return false
             }
-
-            try {
-                zaos.finish()
-            } catch (e1: IOException) {
-                // ignore
-            }
-
-            try {
-                zaos.flush()
-            } catch (e: IOException) {
-                // ignore
-            }
-
-            try {
-                zaos.close()
-            } catch (e: IOException) {
-                // ignore
-            }
-
             return true
         }
 
@@ -113,21 +88,19 @@ class BluePrintArchiveUtils {
             if (file.isDirectory) {
                 // recursive call
                 val files = file.listFiles()
-                for (file2 in files!!) {
-                    recurseFiles(root, file2, zaos, absolute)
+                for (fileChild in files!!) {
+                    recurseFiles(root, fileChild, zaos, absolute)
                 }
             } else if (!file.name.endsWith(".zip") && !file.name.endsWith(".ZIP")) {
-                var filename: String? = null
-                if (absolute) {
-                    filename = file.absolutePath.substring(root.absolutePath.length)
+                val filename = if (absolute) {
+                    file.absolutePath.substring(root.absolutePath.length)
                 } else {
-                    filename = file.name
+                    file.name
                 }
                 val zae = ZipArchiveEntry(filename)
-                zae.setSize(file.length())
+                zae.size = file.length()
                 zaos.putArchiveEntry(zae)
-                val fis = FileInputStream(file)
-                IOUtils.copy(fis, zaos)
+                FileInputStream(file).use { IOUtils.copy(it, zaos) }
                 zaos.closeArchiveEntry()
             }
         }
index 05f6a2d..a2c7344 100644 (file)
@@ -17,6 +17,7 @@
 package org.onap.ccsdk.apps.blueprintsprocessor.functions.netconf.executor
 
 import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.node.JsonNodeFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ActionIdentifiers
@@ -46,6 +47,8 @@ class ComponentNetconfExecutorTest {
     fun testComponentNetconfExecutor() {
 
         val executionServiceInput = ExecutionServiceInput()
+        executionServiceInput.payload = JsonNodeFactory.instance.objectNode()
+
         val commonHeader = CommonHeader()
         commonHeader.requestId = "1234"
         executionServiceInput.commonHeader = commonHeader
index 07591a5..7684b2b 100644 (file)
@@ -17,6 +17,7 @@
 package org.onap.ccsdk.apps.blueprintsprocessor.functions.python.executor
 
 import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.node.JsonNodeFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ActionIdentifiers
@@ -43,8 +44,9 @@ class ComponentJythonExecutorTest {
 
     @Test
     fun testPythonComponentInjection() {
-
         val executionServiceInput = ExecutionServiceInput()
+        executionServiceInput.payload = JsonNodeFactory.instance.objectNode()
+
         val commonHeader = CommonHeader()
         commonHeader.requestId = "1234"
         executionServiceInput.commonHeader = commonHeader
@@ -68,6 +70,7 @@ class ComponentJythonExecutorTest {
         componentJythonExecutor.bluePrintRuntimeService = bluePrintRuntimeService
         componentJythonExecutor.stepName = "activate-jython"
 
+
         componentJythonExecutor.apply(executionServiceInput)
 
     }
index 0a67e87..35d4e67 100644 (file)
@@ -21,7 +21,14 @@ import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceInp
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceOutput
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.MediaType
-import org.springframework.web.bind.annotation.*
+import org.springframework.http.codec.multipart.FilePart
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestMethod
+import org.springframework.web.bind.annotation.RequestPart
+import org.springframework.web.bind.annotation.ResponseBody
+import org.springframework.web.bind.annotation.RestController
 import reactor.core.publisher.Mono
 
 @RestController
@@ -32,13 +39,23 @@ class ExecutionServiceController {
     lateinit var executionServiceHandler: ExecutionServiceHandler
 
 
-    @RequestMapping(path = arrayOf("/ping"), method = arrayOf(RequestMethod.GET), produces = arrayOf(MediaType.APPLICATION_JSON_VALUE))
+    @RequestMapping(path = ["/ping"], method = [RequestMethod.GET], produces = [MediaType.APPLICATION_JSON_VALUE])
     @ResponseBody
     fun ping(): Mono<String> {
         return Mono.just("Success")
     }
 
-    @RequestMapping(path = arrayOf("/process"), method = arrayOf(RequestMethod.POST), produces = arrayOf(MediaType.APPLICATION_JSON_VALUE))
+    @PostMapping(path = ["/upload"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
+    @ApiOperation(value = "Upload CBA", notes = "Takes a File and load it in the runtime database")
+    @ResponseBody
+    fun upload(@RequestPart("file") parts: Mono<FilePart>): Mono<String> {
+        return parts
+                .filter { it is FilePart }
+                .ofType(FilePart::class.java)
+                .flatMap(executionServiceHandler::upload)
+    }
+
+    @RequestMapping(path = ["/process"], method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE])
     @ApiOperation(value = "Resolve Resource Mappings", notes = "Takes the blueprint information and process as per the payload")
     @ResponseBody
     fun process(@RequestBody executionServiceInput: ExecutionServiceInput): Mono<ExecutionServiceOutput> {
index 56a6393..5690374 100644 (file)
 
 package org.onap.ccsdk.apps.blueprintsprocessor.selfservice.api
 
+import org.onap.ccsdk.apps.blueprintsprocessor.core.BluePrintCoreConfiguration
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceInput
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceOutput
-import org.onap.ccsdk.apps.controllerblueprints.core.interfaces.BluePrintCatalogService
+import org.onap.ccsdk.apps.blueprintsprocessor.selfservice.api.utils.saveCBAFile
 import org.onap.ccsdk.apps.blueprintsprocessor.services.workflow.BlueprintDGExecutionService
+import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintException
+import org.onap.ccsdk.apps.controllerblueprints.core.interfaces.BluePrintCatalogService
+import org.onap.ccsdk.apps.controllerblueprints.core.utils.BluePrintFileUtils
 import org.onap.ccsdk.apps.controllerblueprints.core.utils.BluePrintMetadataUtils
 import org.slf4j.LoggerFactory
+import org.springframework.http.codec.multipart.FilePart
 import org.springframework.stereotype.Service
+import reactor.core.publisher.Mono
 
 @Service
-class ExecutionServiceHandler(private val bluePrintCatalogService: BluePrintCatalogService,
+class ExecutionServiceHandler(private val bluePrintCoreConfiguration: BluePrintCoreConfiguration,
+                              private val bluePrintCatalogService: BluePrintCatalogService,
                               private val blueprintDGExecutionService: BlueprintDGExecutionService) {
 
     private val log = LoggerFactory.getLogger(ExecutionServiceHandler::class.toString())
 
+    fun upload(filePart: FilePart): Mono<String> {
+        try {
+            val archivedPath = BluePrintFileUtils.getCbaStorageDirectory(bluePrintCoreConfiguration.archivePath)
+            val cbaPath = saveCBAFile(filePart, archivedPath)
+            bluePrintCatalogService.saveToDatabase(cbaPath.toFile()).let {
+                return Mono.just("{\"status\": \"Successfully uploaded blueprint with id($it)\"}")
+            }
+        } catch (e: Exception) {
+            return Mono.error<String>(BluePrintException("Error uploading the CBA file.", e))
+        }
+    }
+
     fun process(executionServiceInput: ExecutionServiceInput): ExecutionServiceOutput {
 
         val requestId = executionServiceInput.commonHeader.requestId
@@ -47,6 +66,4 @@ class ExecutionServiceHandler(private val bluePrintCatalogService: BluePrintCata
 
         return blueprintDGExecutionService.executeDirectedGraph(blueprintRuntimeService, executionServiceInput)
     }
-
-
 }
\ No newline at end of file
  */
 package org.onap.ccsdk.apps.blueprintsprocessor.selfservice.api.utils
 
+import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintException
+import org.springframework.http.codec.multipart.FilePart
+import org.springframework.util.StringUtils
+import java.io.File
+import java.io.IOException
+import java.nio.file.Path
 import java.time.LocalDateTime
 import java.time.ZoneId
 import java.time.format.DateTimeFormatter
+import java.util.*
 
 
 fun currentTimestamp(): String {
@@ -25,3 +32,28 @@ fun currentTimestamp(): String {
     val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
     return formatter.format(now)
 }
+
+
+@Throws(BluePrintException::class, IOException::class)
+fun saveCBAFile(filePart: FilePart, targetDirectory: Path): Path {
+
+    val fileName = StringUtils.cleanPath(filePart.filename())
+
+    if (StringUtils.getFilenameExtension(fileName) != "zip") {
+        throw BluePrintException("Invalid file extension required ZIP")
+    }
+
+    val changedFileName = UUID.randomUUID().toString() + ".zip"
+
+    val targetLocation = targetDirectory.resolve(changedFileName)
+
+    val file = File(targetLocation.toString())
+    if (file.exists()) {
+        file.delete()
+    }
+    file.createNewFile()
+
+    filePart.transferTo(file)
+
+    return targetLocation
+}
\ No newline at end of file
index 15e6ca9..de12014 100644 (file)
 
 package org.onap.ccsdk.apps.blueprintsprocessor.selfservice.api
 
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.onap.ccsdk.apps.blueprintsprocessor.core.BluePrintCoreConfiguration
 import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceInput
 import org.onap.ccsdk.apps.controllerblueprints.core.interfaces.BluePrintCatalogService
 import org.onap.ccsdk.apps.controllerblueprints.core.utils.JacksonUtils
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
 import org.springframework.context.annotation.ComponentScan
-import org.springframework.test.annotation.DirtiesContext
+import org.springframework.core.io.ByteArrayResource
+import org.springframework.http.client.MultipartBodyBuilder
+import org.springframework.test.context.ContextConfiguration
 import org.springframework.test.context.TestPropertySource
 import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.function.BodyInserters
+import java.nio.file.Files
 import java.nio.file.Paths
 import kotlin.test.assertTrue
 
-@Ignore
 @RunWith(SpringRunner::class)
-@DirtiesContext
-@EnableAutoConfiguration
+@WebFluxTest
+@ContextConfiguration(classes = [ExecutionServiceHandler::class, BluePrintCoreConfiguration::class, BluePrintCatalogService::class])
 @ComponentScan(basePackages = ["org.onap.ccsdk.apps.blueprintsprocessor", "org.onap.ccsdk.apps.controllerblueprints"])
 @TestPropertySource(locations = ["classpath:application-test.properties"])
 class ExecutionServiceHandlerTest {
 
-    @Autowired
-    lateinit var executionServiceHandler: ExecutionServiceHandler
-
     @Autowired
     lateinit var blueprintCatalog: BluePrintCatalogService
+    @Autowired
+    lateinit var webTestClient: WebTestClient
+
 
     @Test
-    fun testProcess() {
+    fun `test rest upload blueprint`() {
         val file = Paths.get("./src/test/resources/test-cba.zip").toFile()
         assertTrue(file.exists(), "couldnt get file ${file.absolutePath}")
 
+        val body = MultipartBodyBuilder().apply {
+            part("file", object : ByteArrayResource(Files.readAllBytes(Paths.get("./src/test/resources/test-cba.zip"))) {
+                override fun getFilename(): String {
+                    return "test-cba.zip"
+                }
+            })
+        }.build()
+
+        webTestClient
+                .post()
+                .uri("/api/v1/execution-service/upload")
+                .body(BodyInserters.fromMultipartData(body))
+                .exchange()
+                .expectStatus().isOk
+    }
+
+    @Test
+    fun `test rest process`() {
+        val file = Paths.get("./src/test/resources/test-cba.zip").toFile()
+        assertTrue(file.exists(), "couldnt get file ${file.absolutePath}")
         blueprintCatalog.saveToDatabase(file)
 
         val executionServiceInput = JacksonUtils.readValueFromClassPathFile("execution-input/default-input.json", ExecutionServiceInput::class.java)!!
-
-        executionServiceHandler.process(executionServiceInput)
+        webTestClient
+                .post()
+                .uri("/api/v1/execution-service/process")
+                .body(BodyInserters.fromObject(executionServiceInput))
+                .exchange()
+                .expectStatus().isOk
     }
-
 }
\ No newline at end of file
diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/mock/Mock.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/apps/blueprintsprocessor/selfservice/api/mock/Mock.kt
new file mode 100644 (file)
index 0000000..c54e617
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 Bell Canada.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onap.ccsdk.apps.blueprintsprocessor.selfservice.api.mock
+
+import org.onap.ccsdk.apps.blueprintsprocessor.core.api.data.ExecutionServiceInput
+import org.onap.ccsdk.apps.blueprintsprocessor.services.execution.AbstractComponentFunction
+import org.onap.ccsdk.apps.controllerblueprints.core.asJsonPrimitive
+import org.slf4j.LoggerFactory
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+open class MockComponentConfiguration {
+
+    @Bean(name = ["component-resource-assignment", "component-netconf-executor", "component-jython-executor"])
+    open fun createComponentFunction(): AbstractComponentFunction {
+        return MockComponentFunction()
+    }
+}
+
+class MockComponentFunction : AbstractComponentFunction() {
+
+    private val log = LoggerFactory.getLogger(MockComponentFunction::class.java)
+
+    override fun process(executionRequest: ExecutionServiceInput) {
+        log.info("Processing component : $operationInputs")
+
+        bluePrintRuntimeService.setNodeTemplateAttributeValue(nodeTemplateName,
+                "assignment-params", "params".asJsonPrimitive())
+    }
+
+    override fun recover(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
+        log.info("Recovering component..")
+    }
+}
\ No newline at end of file
index c9e147b..f7c901d 100644 (file)
@@ -53,17 +53,17 @@ abstract class AbstractComponentFunction : BlueprintFunctionNode<ExecutionServic
         return stepName\r
     }\r
 \r
-    override fun prepareRequest(executionServiceInput: ExecutionServiceInput): ExecutionServiceInput {\r
+    override fun prepareRequest(executionRequest: ExecutionServiceInput): ExecutionServiceInput {
         checkNotNull(bluePrintRuntimeService) { "failed to prepare blueprint runtime" }\r
 \r
         check(stepName.isNotEmpty()) { "failed to assign step name" }\r
 \r
-        this.executionServiceInput = executionServiceInput\r
+        this.executionServiceInput = executionRequest
 \r
-        processId = executionServiceInput.commonHeader.requestId\r
+        processId = executionRequest.commonHeader.requestId
         check(processId.isNotEmpty()) { "couldn't get process id for step($stepName)" }\r
 \r
-        workflowName = executionServiceInput.actionIdentifiers.actionName\r
+        workflowName = executionRequest.actionIdentifiers.actionName
         check(workflowName.isNotEmpty()) { "couldn't get action name for step($stepName)" }\r
 \r
         log.info("preparing request id($processId) for workflow($workflowName) step($stepName)")\r
@@ -88,12 +88,14 @@ abstract class AbstractComponentFunction : BlueprintFunctionNode<ExecutionServic
 \r
         this.operationInputs.putAll(operationResolvedProperties)\r
 \r
-        return executionServiceInput\r
+        return executionRequest
     }\r
 \r
     override fun prepareResponse(): ExecutionServiceOutput {\r
         log.info("Preparing Response...")\r
         executionServiceOutput.commonHeader = executionServiceInput.commonHeader\r
+        executionServiceOutput.actionIdentifiers = executionServiceInput.actionIdentifiers
+        executionServiceOutput.payload = executionServiceInput.payload
 \r
         // Resolve the Output Expression\r
         val stepOutputs = bluePrintRuntimeService\r