diff options
Diffstat (limited to 'ms/blueprintsprocessor')
16 files changed, 733 insertions, 158 deletions
diff --git a/ms/blueprintsprocessor/application/pom.xml b/ms/blueprintsprocessor/application/pom.xml index 314b09c42..120b948be 100755 --- a/ms/blueprintsprocessor/application/pom.xml +++ b/ms/blueprintsprocessor/application/pom.xml @@ -132,6 +132,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.schibsted.spt.data</groupId> + <artifactId>jslt</artifactId> + <version>0.1.8</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt index 0a57277ea..ad4173c9e 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt @@ -19,7 +19,9 @@ */ package org.onap.ccsdk.cds.blueprintsprocessor +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.MissingNode import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.argThat import com.nhaarman.mockitokotlin2.atLeast @@ -53,15 +55,17 @@ import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit4.rules.SpringClassRule import org.springframework.test.context.junit4.rules.SpringMethodRule +import org.springframework.test.web.reactive.server.EntityExchangeResult import org.springframework.test.web.reactive.server.WebTestClient -import org.yaml.snakeyaml.Yaml import reactor.core.publisher.Mono import java.io.File -import java.nio.file.Path +import java.nio.charset.StandardCharsets import java.nio.file.Paths import kotlin.test.BeforeTest import kotlin.test.Test +// Only one runner can be configured with jUnit 4. We had to replace the SpringRunner by equivalent jUnit rules. +// See more on https://docs.spring.io/autorepo/docs/spring-framework/current/spring-framework-reference/testing.html#testcontext-junit4-rules @RunWith(Parameterized::class) // Set blueprintsprocessor.httpPort=0 to trigger a random port selection @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -71,8 +75,7 @@ import kotlin.test.Test TestSecuritySettings.ServerContextInitializer::class ]) @TestPropertySource(locations = ["classpath:application-test.properties"]) -@Suppress("UNCHECKED_CAST") -class BlueprintsAcceptanceTests(private val blueprintName: String, private val filename: String) { +class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) { companion object { const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints" @@ -82,11 +85,15 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f @JvmField val springClassRule = SpringClassRule() - val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTests::class.java) + val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java) + /** + * Generates the parameters to create a test instance for every blueprint found under UAT_BLUEPRINTS_BASE_DIR + * that contains the proper UAT definition file. + */ @Parameterized.Parameters(name = "{index} {0}") @JvmStatic - fun filenames(): List<Array<String>> { + fun testParameters(): List<Array<String>> { return File(UAT_BLUEPRINTS_BASE_DIR) .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile } ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) } @@ -119,38 +126,31 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f @Test fun testBlueprint() { - val yaml: Map<String, *> = loadYaml(Paths.get(filename, EMBEDDED_UAT_FILE)) + val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE)) uploadBlueprint(blueprintName) // Configure mocked external services - val services = yaml["external-services"] as List<Map<String, *>>? ?: emptyList() - val expectationPerClient = services.map { service -> - val selector = service["selector"] as String - val expectations = (service["expectations"] as List<Map<String, *>>).map { - parseExpectation(it) - } - val mockClient = createRestClientMock(selector, expectations) - mockClient to expectations - }.toMap() + val expectationPerClient = uat.externalServices.associateBy( + { service -> createRestClientMock(service.selector, service.expectations) }, + { service -> service.expectations } + ) // Run processes - for (process in (yaml["processes"] as List<Map<String, *>>)) { - val processName = process["name"] - log.info("Executing process '$processName'") - val request = mapper.writeValueAsString(process["request"]) - val expectedResponse = mapper.writeValueAsString(process["expectedResponse"]) - processBlueprint(request, expectedResponse) + for (process in uat.processes) { + log.info("Executing process '${process.name}'") + processBlueprint(process.request, process.expectedResponse, + JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec)) } - // Validate request payloads + // Validate request payloads to external services for ((mockClient, expectations) in expectationPerClient) { expectations.forEach { expectation -> verify(mockClient, atLeastOnce()).exchangeResource( - eq(expectation.method), - eq(expectation.path), - argThat { assertJsonEqual(expectation.expectedRequestBody, this) }, - expectation.requestHeadersMatcher()) + eq(expectation.request.method), + eq(expectation.request.path), + argThat { assertJsonEqual(expectation.request.body, this) }, + expectation.request.requestHeadersMatcher()) } // Don't mind the invocations to the overloaded exchangeResource(String, String, String) verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any()) @@ -158,7 +158,8 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f } } - private fun createRestClientMock(selector: String, restExpectations: List<RestExpectation>): BlueprintWebClientService { + private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>) + : BlueprintWebClientService { val restClient = mock<BlueprintWebClientService>(verboseLogging = true) // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>) @@ -171,11 +172,11 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f } for (expectation in restExpectations) { whenever(restClient.exchangeResource( - eq(expectation.method), - eq(expectation.path), + eq(expectation.request.method), + eq(expectation.request.path), any(), any())) - .thenReturn(WebClientResponse(expectation.statusCode, expectation.responseBody)) + .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString())) } whenever(restClientFactory.blueprintWebClientService(selector)) @@ -194,17 +195,20 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f .expectStatus().isOk } - private fun processBlueprint(request: String, expectedResponse: String) { + private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode, + responseNormalizer: (String) -> String) { webTestClient .post() .uri("/api/v1/execution-service/process") .header("Authorization", TestSecuritySettings.clientAuthToken()) .contentType(MediaType.APPLICATION_JSON_UTF8) - .body(Mono.just(request), String::class.java) + .body(Mono.just(request.toString()), String::class.java) .exchange() .expectStatus().isOk .expectBody() - .json(expectedResponse) + .consumeWith { response -> + assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response))) + } } private fun getBlueprintAsResource(blueprintName: String): Resource { @@ -216,65 +220,21 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f } } - private fun loadYaml(path: Path): Map<String, Any> { - return path.toFile().reader().use { reader -> - Yaml().load(reader) - } - } - - private fun assertJsonEqual(expected: Any, actual: String): Boolean { - if (actual != expected) { - // assertEquals throws an exception whenever match fails - JSONAssert.assertEquals(mapper.writeValueAsString(expected), actual, JSONCompareMode.LENIENT) + private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean { + if ((actual == "") && (expected is MissingNode)) { + return true } + JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT) + // assertEquals throws an exception whenever match fails return true } - private fun parseExpectation(expectation: Map<String, *>): RestExpectation { - val request = expectation["request"] as Map<String, Any> - val method = request["method"] as String - val path = joinPath(request.getValue("path")) - val contentType = request["content-type"] as String? - val requestBody = request.getOrDefault("body", "") - - val response = expectation["response"] as Map<String, Any>? ?: emptyMap() - val status = response["status"] as Int? ?: 200 - val responseBody = when (val body = response["body"] ?: "") { - is String -> body - else -> mapper.writeValueAsString(body) - } - - return RestExpectation(method, path, contentType, requestBody, status, responseBody) - } - - /** - * Join a multilevel lists of strings. - * Example: joinPath(listOf("a", listOf("b", "c"), "d")) will result in "a/b/c/d". - */ - private fun joinPath(any: Any): String { - fun recursiveJoin(any: Any, sb: StringBuilder): StringBuilder { - when (any) { - is List<*> -> any.filterNotNull().forEach { recursiveJoin(it, sb) } - is String -> { - if (sb.isNotEmpty()) { - sb.append('/') - } - sb.append(any) - } - else -> throw IllegalArgumentException("Unsupported type: ${any.javaClass}") - } - return sb - } - - return recursiveJoin(any, StringBuilder()).toString() - } - - data class RestExpectation(val method: String, val path: String, val contentType: String?, - val expectedRequestBody: Any, - val statusCode: Int, val responseBody: String) { - - fun requestHeadersMatcher(): Map<String, String> { - return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any() + private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String { + val body = result.responseBody + if ((body == null) || body.isEmpty()) { + return "" } + val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8 + return String(body, charset) } }
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt index 4576f2761..3c517e6ac 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt @@ -1,3 +1,22 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ package org.onap.ccsdk.cds.blueprintsprocessor import org.junit.rules.TemporaryFolder diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt new file mode 100644 index 000000000..69673f931 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt @@ -0,0 +1,79 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.ccsdk.cds.blueprintsprocessor + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.ContainerNode +import com.fasterxml.jackson.databind.node.MissingNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.schibsted.spt.data.jslt.Parser + +class JsonNormalizer { + + companion object { + + fun getNormalizer(mapper: ObjectMapper, jsltSpec: JsonNode): (String) -> String { + if (jsltSpec is MissingNode) { + return { it } + } + return { s: String -> + val input = mapper.readTree(s) + val expandedJstlSpec = expandJstlSpec(jsltSpec) + val jslt = Parser.compileString(expandedJstlSpec) + val output = jslt.apply(input) + output.toString() + } + } + + /** + * Creates an extended JSTL spec by appending the "*: ." wildcard pattern to every inner JSON object, and + * removing the extra quotes added by the standard YAML/JSON converters on fields prefixed by "?". + * + * @param jstlSpec the JSTL spec as a structured JSON object. + * @return the string representation of the extended JSTL spec. + */ + private fun expandJstlSpec(jstlSpec: JsonNode): String { + val extendedJstlSpec = updateObjectNodes(jstlSpec, "*", ".") + return extendedJstlSpec.toString() + // Handle the "?" as a prefix to literal/non-quoted values + .replace("\"\\?([^\"]+)\"".toRegex(), "$1") + // Also, remove the quotes added by Jackson for key and value of the wildcard matcher + .replace("\"([.*])\"".toRegex(), "$1") + } + + /** + * Expands a structured JSON object, by adding the given key and value to every nested ObjectNode. + * + * @param jsonNode the root node. + * @param fieldName the fixed field name. + * @param fieldValue the fixed field value. + */ + private fun updateObjectNodes(jsonNode: JsonNode, fieldName: String, fieldValue: String): JsonNode { + if (jsonNode is ContainerNode<*>) { + (jsonNode as? ObjectNode)?.put(fieldName, fieldValue) + jsonNode.forEach { child -> + updateObjectNodes(child, fieldName, fieldValue) + } + } + return jsonNode + } + } +} diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt new file mode 100644 index 000000000..1a232f2d3 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.ccsdk.cds.blueprintsprocessor + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.deser.std.StdDeserializer + +class PathDeserializer : StdDeserializer<String>(String::class.java) { + override fun deserialize(jp: JsonParser, ctxt: DeserializationContext?): String { + val path = jp.codec.readValue(jp, Any::class.java) + return flatJoin(path) + } + + /** + * Join a multilevel lists of strings. + * Example: flatJoin(listOf("a", listOf("b", "c"), "d")) will result in "a/b/c/d". + */ + private fun flatJoin(path: Any): String { + fun flatJoinTo(sb: StringBuilder, path: Any): StringBuilder { + when (path) { + is List<*> -> path.filterNotNull().forEach { flatJoinTo(sb, it) } + is String -> { + if (sb.isNotEmpty()) { + sb.append('/') + } + sb.append(path) + } + else -> throw IllegalArgumentException("Unsupported type: ${path.javaClass}") + } + return sb + } + return flatJoinTo(StringBuilder(), path).toString() + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt new file mode 100644 index 000000000..ce2061168 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.ccsdk.cds.blueprintsprocessor + +import com.fasterxml.jackson.annotation.JsonAlias +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.node.MissingNode +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.eq +import org.yaml.snakeyaml.Yaml +import java.nio.file.Path + +data class ProcessDefinition(val name: String, val request: JsonNode, val expectedResponse: JsonNode, + val responseNormalizerSpec: JsonNode = MissingNode.getInstance()) + +data class RequestDefinition(val method: String, + @JsonDeserialize(using = PathDeserializer::class) + val path: String, + @JsonAlias("content-type") + val contentType: String? = null, + val body: JsonNode = MissingNode.getInstance()) { + fun requestHeadersMatcher(): Map<String, String> { + return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any() + } +} + +data class ResponseDefinition(val status: Int = 200, val body: JsonNode = MissingNode.getInstance()) { + companion object { + val DEFAULT_RESPONSE = ResponseDefinition() + } +} + +data class ExpectationDefinition(val request: RequestDefinition, + val response: ResponseDefinition = ResponseDefinition.DEFAULT_RESPONSE) + +data class ServiceDefinition(val selector: String, val expectations: List<ExpectationDefinition>) + +data class UatDefinition(val processes: List<ProcessDefinition>, + @JsonAlias("external-services") + val externalServices: List<ServiceDefinition> = emptyList()) { + + companion object { + fun load(mapper: ObjectMapper, path: Path): UatDefinition { + return path.toFile().reader().use { reader -> + mapper.convertValue(Yaml().load(reader), UatDefinition::class.java) + } + } + } +} diff --git a/ms/blueprintsprocessor/functions/ansible-awx-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/ansible/executor/ComponentRemoteAnsibleExecutor.kt b/ms/blueprintsprocessor/functions/ansible-awx-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/ansible/executor/ComponentRemoteAnsibleExecutor.kt index 947a9630d..743aa714b 100644 --- a/ms/blueprintsprocessor/functions/ansible-awx-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/ansible/executor/ComponentRemoteAnsibleExecutor.kt +++ b/ms/blueprintsprocessor/functions/ansible-awx-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/ansible/executor/ComponentRemoteAnsibleExecutor.kt @@ -24,10 +24,7 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInpu import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction -import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive -import org.onap.ccsdk.cds.controllerblueprints.core.asJsonString -import org.onap.ccsdk.cds.controllerblueprints.core.isNotNull -import org.onap.ccsdk.cds.controllerblueprints.core.rootFieldsToMap +import org.onap.ccsdk.cds.controllerblueprints.core.* import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory @@ -68,6 +65,7 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe // input fields names accepted by this executor const val INPUT_ENDPOINT_SELECTOR = "endpoint-selector" const val INPUT_JOB_TEMPLATE_NAME = "job-template-name" + const val INPUT_WORKFLOW_JOB_TEMPLATE_NAME = "workflow-job-template-id" const val INPUT_LIMIT_TO_HOST = "limit" const val INPUT_INVENTORY = "inventory" const val INPUT_EXTRA_VARS = "extra-vars" @@ -85,12 +83,20 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe try { val restClientService = getAWXRestClient() - val jobTemplateName = getOperationInput(INPUT_JOB_TEMPLATE_NAME).asText() - val jtId = lookupJobTemplateIDByName(restClientService, jobTemplateName) + // Get either a job template name or a workflow template name property + var workflowURIPrefix = "" + var jobTemplateName = getOperationInput(INPUT_JOB_TEMPLATE_NAME).returnNullIfMissing()?.textValue() ?: "" + val isWorkflowJT = jobTemplateName.isBlank() + if (isWorkflowJT) { + jobTemplateName = getOperationInput(INPUT_WORKFLOW_JOB_TEMPLATE_NAME).asText() + workflowURIPrefix = "workflow_" + } + + val jtId = lookupJobTemplateIDByName(restClientService, jobTemplateName, workflowURIPrefix) if (jtId.isNotEmpty()) { - runJobTemplateOnAWX(restClientService, jobTemplateName, jtId) + runJobTemplateOnAWX(restClientService, jobTemplateName, jtId, workflowURIPrefix) } else { - val message = "Job template ${jobTemplateName} does not exists" + val message = "Workflow/Job template ${jobTemplateName} does not exists" log.error(message) setNodeOutputErrors(ATTRIBUTE_EXEC_CMD_STATUS_ERROR, message) } @@ -135,9 +141,10 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe /** * Finds the job template ID based on the job template name provided in the request */ - private fun lookupJobTemplateIDByName(awxClient: BlueprintWebClientService, job_template_name: String?): String { + private fun lookupJobTemplateIDByName(awxClient: BlueprintWebClientService, job_template_name: String?, + workflowPrefix : String) : String { val encodedJTName = URI(null, null, - "/api/v2/job_templates/${job_template_name}/", + "/api/v2/${workflowPrefix}job_templates/${job_template_name}/", null, null).rawPath // Get Job Template details by name @@ -152,19 +159,20 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe * its execution. Finally, it retrieves the job results via the stdout api. * The status and output attributes are populated in the process. */ - private fun runJobTemplateOnAWX(awxClient: BlueprintWebClientService, job_template_name: String?, jtId: String) { + private fun runJobTemplateOnAWX(awxClient: BlueprintWebClientService, job_template_name: String?, jtId: String, + workflowPrefix : String) { setNodeOutputProperties("preparing".asJsonPrimitive(), "".asJsonPrimitive()) // Get Job Template requirements - var response = awxClient.exchangeResource(GET, "/api/v2/job_templates/${jtId}/launch/", "") + var response = awxClient.exchangeResource(GET, "/api/v2/${workflowPrefix}job_templates/${jtId}/launch/", "") // FIXME: handle non-successful SC val jtLaunchReqs: JsonNode = mapper.readTree(response.body) - val payload = prepareLaunchPayload(awxClient, jtLaunchReqs) + val payload = prepareLaunchPayload(awxClient, jtLaunchReqs, workflowPrefix.isBlank()) log.info("Running job with $payload, for requestId $processId.") // Launch the job for the targeted template var jtLaunched: JsonNode = JacksonUtils.objectMapper.createObjectNode() - response = awxClient.exchangeResource(POST, "/api/v2/job_templates/${jtId}/launch/", payload) + response = awxClient.exchangeResource(POST, "/api/v2/${workflowPrefix}job_templates/${jtId}/launch/", payload) if (response.status in HTTP_SUCCESS) { jtLaunched = mapper.readTree(response.body) val fieldsIgnored: JsonNode = jtLaunched.at("/ignored_fields") @@ -180,7 +188,7 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe var jobStatus = "unknown" var jobEndTime = "null" while (jobEndTime == "null") { - response = awxClient.exchangeResource(GET, "/api/v2/jobs/${jobId}/", "") + response = awxClient.exchangeResource(GET, "/api/v2/${workflowPrefix}jobs/${jobId}/", "") val jobLaunched: JsonNode = mapper.readTree(response.body) jobStatus = jobLaunched.at("/status").asText() jobEndTime = jobLaunched.at("/finished").asText() @@ -189,12 +197,10 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe log.info("Execution of job template $job_template_name in job #$jobId finished with status ($jobStatus) for requestId $processId") - // Get job execution results (stdout) - val plainTextHeaders = mutableMapOf<String, String>() - plainTextHeaders["Content-Type"] = "text/plain ;utf-8" - response = awxClient.exchangeResource(GET, "/api/v2/jobs/${jobId}/stdout/?format=txt", "", plainTextHeaders) + // Get workflow/job execution results + val collectedOutput = extractJobRunResponse(awxClient, jobId, workflowPrefix) - setNodeOutputProperties(jobStatus.asJsonPrimitive(), response.body.asJsonPrimitive()) + setNodeOutputProperties(jobStatus.asJsonPrimitive(), collectedOutput.asJsonPrimitive()) } else { // The job template requirements were not fulfilled with the values passed in. The message below will // provide more information via the response, like the ignored_fields, or variables_needed_to_start, @@ -207,42 +213,77 @@ open class ComponentRemoteAnsibleExecutor(private val blueprintRestLibPropertySe } /** + * Extracts the response from either a job stdout call OR collects the workflow run output + */ + private fun extractJobRunResponse(awxClient: BlueprintWebClientService, jobId: String, workflowPrefix: String): String { + + // First, collect all job ID from either the job template run or the workflow nodes that ran + var jobIds : Array<String> + var collectedResponses = StringBuilder() + if (workflowPrefix.isNotEmpty()) { + var response = awxClient.exchangeResource(GET, "/api/v2/${workflowPrefix}jobs/${jobId}/workflow_nodes/", "") + val jobDetails = mapper.readTree(response.body).at("/results") + jobIds = emptyArray() + for (jobDetail in jobDetails.elements()) { + jobIds = jobIds.plus( jobDetail.at("/summary_fields/job/id").asText() ) + } + } else { + jobIds = arrayOf(jobId) + } + + // Then collect the response text from the corresponding jobIds + val plainTextHeaders = mutableMapOf<String, String>() + plainTextHeaders["Content-Type"] = "text/plain ;utf-8" + for (aJobId in jobIds) { + var response = awxClient.exchangeResource(GET, "/api/v2/jobs/${aJobId}/stdout/?format=txt", "", plainTextHeaders) + collectedResponses.append("Output for job ${aJobId}:") + collectedResponses.append(response.body) + } + return collectedResponses.toString() + } + + /** * Prepares the JSON payload expected by the job template api, * by applying the overrides that were provided * and allowed by the template definition flags in jtLaunchReqs */ - private fun prepareLaunchPayload(awxClient: BlueprintWebClientService, jtLaunchReqs: JsonNode): String { + private fun prepareLaunchPayload(awxClient: BlueprintWebClientService, jtLaunchReqs: JsonNode, + isWorkflow : Boolean): String { val payload = JacksonUtils.objectMapper.createObjectNode() // Parameter defaults - val limitProp = getOptionalOperationInput(INPUT_LIMIT_TO_HOST) - val tagsProp = getOptionalOperationInput(INPUT_TAGS) - val skipTagsProp = getOptionalOperationInput(INPUT_SKIP_TAGS) val inventoryProp = getOptionalOperationInput(INPUT_INVENTORY) val extraArgs = getOperationInput(INPUT_EXTRA_VARS) - val askLimitOnLaunch = jtLaunchReqs.at("/ask_limit_on_launch").asBoolean() - if (askLimitOnLaunch && limitProp.isNotNull()) { - payload.set(INPUT_LIMIT_TO_HOST, limitProp) - } - val askTagsOnLaunch = jtLaunchReqs.at("/ask_tags_on_launch").asBoolean() - if (askTagsOnLaunch && tagsProp.isNotNull()) { - payload.set(INPUT_TAGS, tagsProp) - } - if (askTagsOnLaunch && skipTagsProp.isNotNull()) { - payload.set("skip_tags", skipTagsProp) + if (!isWorkflow) { + val limitProp = getOptionalOperationInput(INPUT_LIMIT_TO_HOST) + val tagsProp = getOptionalOperationInput(INPUT_TAGS) + val skipTagsProp = getOptionalOperationInput(INPUT_SKIP_TAGS) + + val askLimitOnLaunch = jtLaunchReqs.at("/ask_limit_on_launch").asBoolean() + if (askLimitOnLaunch && limitProp.isNotNull()) { + payload.set(INPUT_LIMIT_TO_HOST, limitProp) + } + val askTagsOnLaunch = jtLaunchReqs.at("/ask_tags_on_launch").asBoolean() + if (askTagsOnLaunch && tagsProp.isNotNull()) { + payload.set(INPUT_TAGS, tagsProp) + } + if (askTagsOnLaunch && skipTagsProp.isNotNull()) { + payload.set("skip_tags", skipTagsProp) + } } + val askInventoryOnLaunch = jtLaunchReqs.at("/ask_inventory_on_launch").asBoolean() if (askInventoryOnLaunch && inventoryProp.isNotNull()) { var inventoryKeyId = if (inventoryProp is TextNode) { - resolveInventoryIdByName(awxClient, inventoryProp!!.textValue())?.asJsonPrimitive() + resolveInventoryIdByName(awxClient, inventoryProp.textValue())?.asJsonPrimitive() } else { inventoryProp } payload.set(INPUT_INVENTORY, inventoryKeyId) } val askVariablesOnLaunch = jtLaunchReqs.at("/ask_variables_on_launch").asBoolean() - if (askVariablesOnLaunch && extraArgs != null) { + if (askVariablesOnLaunch) { payload.set("extra_vars", extraArgs) } return payload.asJsonString(false) diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImpl.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImpl.kt index fcf0558c7..cde919ce8 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImpl.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImpl.kt @@ -29,8 +29,9 @@ import org.springframework.stereotype.Service @Service("bluePrintWorkflowExecutionService") open class BluePrintWorkflowExecutionServiceImpl( - private val componentWorkflowExecutionService: ComponentWorkflowExecutionService, - private val dgWorkflowExecutionService: DGWorkflowExecutionService + private val componentWorkflowExecutionService: ComponentWorkflowExecutionService, + private val dgWorkflowExecutionService: DGWorkflowExecutionService, + private val imperativeWorkflowExecutionService: ImperativeWorkflowExecutionService ) : BluePrintWorkflowExecutionService<ExecutionServiceInput, ExecutionServiceOutput> { private val log = LoggerFactory.getLogger(BluePrintWorkflowExecutionServiceImpl::class.java)!! @@ -51,28 +52,37 @@ open class BluePrintWorkflowExecutionServiceImpl( val input = executionServiceInput.payload.get("$workflowName-request") bluePrintRuntimeService.assignWorkflowInputs(workflowName, input) - // Get the DG Node Template - val nodeTemplateName = bluePrintContext.workflowFirstStepNodeTemplate(workflowName) + val workflow = bluePrintContext.workflowByName(workflowName) - val derivedFrom = bluePrintContext.nodeTemplateNodeType(nodeTemplateName).derivedFrom + val steps = workflow.steps ?: throw BluePrintProcessorException("could't get steps for workflow($workflowName)") - log.info("Executing workflow($workflowName) NodeTemplate($nodeTemplateName), derived from($derivedFrom)") - - val executionServiceOutput: ExecutionServiceOutput = when { - derivedFrom.startsWith(BluePrintConstants.MODEL_TYPE_NODE_COMPONENT, true) -> { - componentWorkflowExecutionService - .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, properties) - } - derivedFrom.startsWith(BluePrintConstants.MODEL_TYPE_NODE_WORKFLOW, true) -> { - dgWorkflowExecutionService + /** If workflow has multiple steps, then it is imperative workflow */ + val executionServiceOutput: ExecutionServiceOutput = if (steps.size > 1) { + imperativeWorkflowExecutionService .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, properties) - } - else -> { - throw BluePrintProcessorException("couldn't execute workflow($workflowName) step mapped " + - "to node template($nodeTemplateName) derived from($derivedFrom)") + } else { + // Get the DG Node Template + val nodeTemplateName = bluePrintContext.workflowFirstStepNodeTemplate(workflowName) + + val derivedFrom = bluePrintContext.nodeTemplateNodeType(nodeTemplateName).derivedFrom + + log.info("Executing workflow($workflowName) NodeTemplate($nodeTemplateName), derived from($derivedFrom)") + /** Return ExecutionServiceOutput based on DG node or Component Node */ + when { + derivedFrom.startsWith(BluePrintConstants.MODEL_TYPE_NODE_COMPONENT, true) -> { + componentWorkflowExecutionService + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, properties) + } + derivedFrom.startsWith(BluePrintConstants.MODEL_TYPE_NODE_WORKFLOW, true) -> { + dgWorkflowExecutionService + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, properties) + } + else -> { + throw BluePrintProcessorException("couldn't execute workflow($workflowName) step mapped " + + "to node template($nodeTemplateName) derived from($derivedFrom)") + } } } - executionServiceOutput.commonHeader = executionServiceInput.commonHeader executionServiceOutput.actionIdentifiers = executionServiceInput.actionIdentifiers // Resolve Workflow Outputs diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt new file mode 100644 index 000000000..2a14be216 --- /dev/null +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt @@ -0,0 +1,148 @@ +/* + * Copyright © 2019 IBM. + * + * 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.cds.blueprintsprocessor.services.workflow + +import kotlinx.coroutines.CompletableDeferred +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.Status +import org.onap.ccsdk.cds.controllerblueprints.core.* +import org.onap.ccsdk.cds.controllerblueprints.core.data.EdgeLabel +import org.onap.ccsdk.cds.controllerblueprints.core.data.Graph +import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintWorkflowExecutionService +import org.onap.ccsdk.cds.controllerblueprints.core.service.* +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Service + +@Service("imperativeWorkflowExecutionService") +class ImperativeWorkflowExecutionService( + private val imperativeBluePrintWorkflowService: BluePrintWorkFlowService<ExecutionServiceInput, ExecutionServiceOutput>) + : BluePrintWorkflowExecutionService<ExecutionServiceInput, ExecutionServiceOutput> { + + override suspend fun executeBluePrintWorkflow(bluePrintRuntimeService: BluePrintRuntimeService<*>, + executionServiceInput: ExecutionServiceInput, + properties: MutableMap<String, Any>): ExecutionServiceOutput { + + val bluePrintContext = bluePrintRuntimeService.bluePrintContext() + + val workflowName = executionServiceInput.actionIdentifiers.actionName + + val graph = bluePrintContext.workflowByName(workflowName).asGraph() + + val deferredOutput = CompletableDeferred<ExecutionServiceOutput>() + imperativeBluePrintWorkflowService.executeWorkflow(graph, bluePrintRuntimeService, + executionServiceInput, deferredOutput) + return deferredOutput.await() + } +} + +@Service +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +open class ImperativeBluePrintWorkflowService(private val nodeTemplateExecutionService: NodeTemplateExecutionService) + : AbstractBluePrintWorkFlowService<ExecutionServiceInput, ExecutionServiceOutput>() { + val log = logger(ImperativeBluePrintWorkflowService::class) + + lateinit var bluePrintRuntimeService: BluePrintRuntimeService<*> + lateinit var executionServiceInput: ExecutionServiceInput + lateinit var workflowName: String + lateinit var deferredExecutionServiceOutput: CompletableDeferred<ExecutionServiceOutput> + + override suspend fun executeWorkflow(graph: Graph, bluePrintRuntimeService: BluePrintRuntimeService<*>, + input: ExecutionServiceInput, + output: CompletableDeferred<ExecutionServiceOutput>) { + this.graph = graph + this.bluePrintRuntimeService = bluePrintRuntimeService + this.executionServiceInput = input + this.workflowName = this.executionServiceInput.actionIdentifiers.actionName + this.deferredExecutionServiceOutput = output + this.workflowId = bluePrintRuntimeService.id() + val startMessage = WorkflowExecuteMessage(input, output) + workflowActor().send(startMessage) + } + + override suspend fun initializeWorkflow(input: ExecutionServiceInput): EdgeLabel { + return EdgeLabel.SUCCESS + } + + override suspend fun prepareWorkflowOutput(exception: BluePrintProcessorException?): ExecutionServiceOutput { + val wfStatus = if (exception != null) { + val status = Status() + status.message = BluePrintConstants.STATUS_FAILURE + status.errorMessage = exception.message + status + } else { + val status = Status() + status.message = BluePrintConstants.STATUS_SUCCESS + status + } + return ExecutionServiceOutput().apply { + commonHeader = executionServiceInput.commonHeader + actionIdentifiers = executionServiceInput.actionIdentifiers + status = wfStatus + } + } + + override suspend fun prepareNodeExecutionMessage(node: Graph.Node) + : NodeExecuteMessage<ExecutionServiceInput, ExecutionServiceOutput> { + val nodeOutput = ExecutionServiceOutput().apply { + commonHeader = executionServiceInput.commonHeader + actionIdentifiers = executionServiceInput.actionIdentifiers + } + return NodeExecuteMessage(node, executionServiceInput, nodeOutput) + } + + override suspend fun prepareNodeSkipMessage(node: Graph.Node) + : NodeSkipMessage<ExecutionServiceInput, ExecutionServiceOutput> { + val nodeOutput = ExecutionServiceOutput().apply { + commonHeader = executionServiceInput.commonHeader + actionIdentifiers = executionServiceInput.actionIdentifiers + } + return NodeSkipMessage(node, executionServiceInput, nodeOutput) + } + + override suspend fun executeNode(node: Graph.Node, nodeInput: ExecutionServiceInput, + nodeOutput: ExecutionServiceOutput): EdgeLabel { + log.info("Executing workflow($workflowName[${this.workflowId}])'s step($${node.id})") + val step = bluePrintRuntimeService.bluePrintContext().workflowStepByName(this.workflowName, node.id) + checkNotEmpty(step.target) { "couldn't get step target for workflow(${this.workflowName})'s step(${node.id})" } + val nodeTemplateName = step.target!! + /** execute node template */ + val executionServiceOutput = nodeTemplateExecutionService + .executeNodeTemplate(bluePrintRuntimeService, nodeTemplateName, nodeInput) + + return when (executionServiceOutput.status.message) { + BluePrintConstants.STATUS_FAILURE -> EdgeLabel.FAILURE + else -> EdgeLabel.SUCCESS + } + } + + override suspend fun skipNode(node: Graph.Node, nodeInput: ExecutionServiceInput, + nodeOutput: ExecutionServiceOutput): EdgeLabel { + return EdgeLabel.SUCCESS + } + + override suspend fun cancelNode(node: Graph.Node, nodeInput: ExecutionServiceInput, + nodeOutput: ExecutionServiceOutput): EdgeLabel { + TODO("not implemented") + } + + override suspend fun restartNode(node: Graph.Node, nodeInput: ExecutionServiceInput, + nodeOutput: ExecutionServiceOutput): EdgeLabel { + TODO("not implemented") + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionService.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionService.kt index af7846340..89732e300 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionService.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionService.kt @@ -23,13 +23,13 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.StepData import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants import org.onap.ccsdk.cds.controllerblueprints.core.putJsonElement +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService import org.slf4j.LoggerFactory -import org.springframework.context.ApplicationContext import org.springframework.stereotype.Service @Service -open class NodeTemplateExecutionService(private val applicationContext: ApplicationContext) { +open class NodeTemplateExecutionService { private val log = LoggerFactory.getLogger(NodeTemplateExecutionService::class.java)!! @@ -48,7 +48,7 @@ open class NodeTemplateExecutionService(private val applicationContext: Applicat "interface($interfaceName) operation($operationName)") // Get the Component Instance - val plugin = applicationContext.getBean(componentName, AbstractComponentFunction::class.java) + val plugin = BluePrintDependencyService.instance<AbstractComponentFunction>(componentName) // Set the Blueprint Service plugin.bluePrintRuntimeService = bluePrintRuntimeService plugin.stepName = nodeTemplateName diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImplTest.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImplTest.kt index c15c054db..436de1b56 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImplTest.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BluePrintWorkflowExecutionServiceImplTest.kt @@ -16,14 +16,21 @@ package org.onap.ccsdk.cds.blueprintsprocessor.services.workflow +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkAll import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput +import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.MockComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintWorkflowExecutionService +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.springframework.beans.factory.annotation.Autowired @@ -41,21 +48,50 @@ class BluePrintWorkflowExecutionServiceImplTest { @Autowired lateinit var bluePrintWorkflowExecutionService: BluePrintWorkflowExecutionService<ExecutionServiceInput, ExecutionServiceOutput> + @Before + fun init() { + mockkObject(BluePrintDependencyService) + every { BluePrintDependencyService.applicationContext.getBean(any()) } returns MockComponentFunction() + } + + @After + fun afterTests() { + unmockkAll() + } + @Test fun testBluePrintWorkflowExecutionService() { runBlocking { val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime("1234", - "./../../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration") + "./../../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration") val executionServiceInput = JacksonUtils.readValueFromClassPathFile("execution-input/resource-assignment-input.json", - ExecutionServiceInput::class.java)!! + ExecutionServiceInput::class.java)!! val executionServiceOutput = bluePrintWorkflowExecutionService - .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) assertNotNull(executionServiceOutput, "failed to get response") assertEquals(BluePrintConstants.STATUS_SUCCESS, executionServiceOutput.status.message, - "failed to get successful response") + "failed to get successful response") + } + } + + @Test + fun testImperativeBluePrintWorkflowExecutionService() { + runBlocking { + val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime("1234", + "./../../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration") + + val executionServiceInput = JacksonUtils.readValueFromClassPathFile("execution-input/imperative-test-input.json", + ExecutionServiceInput::class.java)!! + + val executionServiceOutput = bluePrintWorkflowExecutionService + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) + + assertNotNull(executionServiceOutput, "failed to get response") + assertEquals(BluePrintConstants.STATUS_SUCCESS, executionServiceOutput.status.message, + "failed to get successful response") } } @@ -64,13 +100,13 @@ class BluePrintWorkflowExecutionServiceImplTest { assertFailsWith(exceptionClass = BluePrintProcessorException::class) { runBlocking { val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime("1234", - "./../../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration") + "./../../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration") //service input will have a mislabeled input params, we are expecting to get an error when that happens with a useful error message val executionServiceInput = JacksonUtils.readValueFromClassPathFile("execution-input/resource-assignment-input-missing-resource_assignment_request.json", - ExecutionServiceInput::class.java)!! + ExecutionServiceInput::class.java)!! val executionServiceOutput = bluePrintWorkflowExecutionService - .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) } } } diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintServiceLogicTest.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintServiceLogicTest.kt index 4352277b7..12fd9c7c4 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintServiceLogicTest.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintServiceLogicTest.kt @@ -18,6 +18,7 @@ package org.onap.ccsdk.cds.blueprintsprocessor.services.workflow import kotlinx.coroutines.runBlocking +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput @@ -25,6 +26,7 @@ import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.executor.Compone import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.PrototypeComponentFunction import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.SingletonComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils import org.springframework.beans.factory.annotation.Autowired @@ -44,6 +46,11 @@ class BlueprintServiceLogicTest { @Autowired lateinit var dgWorkflowExecutionService: DGWorkflowExecutionService + @Before + fun init() { + BluePrintDependencyService.inject(applicationContext) + } + @Test fun testExecuteGraphWithSingleComponent() { runBlocking { diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionServiceTest.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionServiceTest.kt new file mode 100644 index 000000000..becd22857 --- /dev/null +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionServiceTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright © 2019 IBM. + * + * 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.cds.blueprintsprocessor.services.workflow + +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkAll +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.nodeTypeComponentScriptExecutor +import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.MockComponentFunction +import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.mockNodeTemplateComponentScriptExecutor +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintTypes +import org.onap.ccsdk.cds.controllerblueprints.core.data.ServiceTemplate +import org.onap.ccsdk.cds.controllerblueprints.core.dsl.serviceTemplate +import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.onap.ccsdk.cds.controllerblueprints.core.normalizedPathName +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintContext +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import kotlin.test.Test +import kotlin.test.assertNotNull + +class ImperativeWorkflowExecutionServiceTest { + val log = logger(ImperativeWorkflowExecutionServiceTest::class) + + @Before + fun init() { + mockkObject(BluePrintDependencyService) + every { BluePrintDependencyService.applicationContext.getBean(any()) } returns MockComponentFunction() + } + + @After + fun afterTests() { + unmockkAll() + } + + fun mockServiceTemplate(): ServiceTemplate { + return serviceTemplate("imperative-test", "1.0.0", + "brindasanth@onap.com", "tosca") { + + topologyTemplate { + nodeTemplate(mockNodeTemplateComponentScriptExecutor("resolve-config", + "cba.wt.imperative.test.ResolveConfig")) + nodeTemplate(mockNodeTemplateComponentScriptExecutor("activate-config", + "cba.wt.imperative.test.ActivateConfig")) + nodeTemplate(mockNodeTemplateComponentScriptExecutor("activate-config-rollback", + "cba.wt.imperative.test.ActivateConfigRollback")) + nodeTemplate(mockNodeTemplateComponentScriptExecutor("activate-licence", + "cba.wt.imperative.test.ActivateLicence")) + + workflow("imperative-test-wf", "Test Imperative flow") { + step("resolve-config", "resolve-config", "") { + success("activate-config") + } + step("activate-config", "activate-config", "") { + success("activate-licence") + failure("activate-config-rollback") + } + step("activate-config-rollback", "activate-config-rollback", "") + step("activate-licence", "activate-licence", "") + } + } + nodeType(BluePrintTypes.nodeTypeComponentScriptExecutor()) + } + } + + @Test + fun testImperativeExecutionService() { + runBlocking { + val serviceTemplate = mockServiceTemplate() + val bluePrintContext = BluePrintContext(serviceTemplate) + bluePrintContext.rootPath = normalizedPathName(".") + bluePrintContext.entryDefinition = "cba.imperative.test.ImperativeTestDefinitions.kt" + val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime("12345", bluePrintContext) + + val executionServiceInput = JacksonUtils + .readValueFromClassPathFile("execution-input/imperative-test-input.json", + ExecutionServiceInput::class.java)!! + + val bluePrintWorkFlowService = ImperativeBluePrintWorkflowService(NodeTemplateExecutionService()) + val imperativeWorkflowExecutionService = ImperativeWorkflowExecutionService(bluePrintWorkFlowService) + val output = imperativeWorkflowExecutionService + .executeBluePrintWorkflow(bluePrintRuntimeService, executionServiceInput, hashMapOf()) + assertNotNull(output) + } + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionServiceTest.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionServiceTest.kt index 05cd99785..24d96629e 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionServiceTest.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/NodeTemplateExecutionServiceTest.kt @@ -15,14 +15,20 @@ */ package org.onap.ccsdk.cds.blueprintsprocessor.services.workflow +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkAll import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock.MockComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils -import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.junit4.SpringRunner import kotlin.test.assertEquals @@ -32,8 +38,16 @@ import kotlin.test.assertNotNull @ContextConfiguration(classes = [WorkflowServiceConfiguration::class]) class NodeTemplateExecutionServiceTest { - @Autowired - lateinit var nodeTemplateExecutionService: NodeTemplateExecutionService + @Before + fun init() { + mockkObject(BluePrintDependencyService) + every { BluePrintDependencyService.applicationContext.getBean(any()) } returns MockComponentFunction() + } + + @After + fun afterTests() { + unmockkAll() + } @Test fun testExecuteNodeTemplate() { @@ -49,7 +63,7 @@ class NodeTemplateExecutionServiceTest { bluePrintRuntimeService.assignWorkflowInputs("resource-assignment", input) val nodeTemplate = "resource-assignment" - + val nodeTemplateExecutionService = NodeTemplateExecutionService() val executionServiceOutput = nodeTemplateExecutionService .executeNodeTemplate(bluePrintRuntimeService, nodeTemplate, executionServiceInput) diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/mock/MockComponentFunction.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/mock/MockComponentFunction.kt index 5dc5b9dba..44751b5b5 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/mock/MockComponentFunction.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/mock/MockComponentFunction.kt @@ -19,6 +19,8 @@ package org.onap.ccsdk.cds.blueprintsprocessor.services.workflow.mock import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.nodeTemplateComponentScriptExecutor +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintTypes import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory @@ -27,6 +29,16 @@ import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Scope import org.springframework.stereotype.Component +fun mockNodeTemplateComponentScriptExecutor(id: String, script: String) = BluePrintTypes.nodeTemplateComponentScriptExecutor(id, + "mock($id) component function") { + definedOperation("") { + inputs { + type("kotlin") + scriptClassReference(script) + } + } +} + @Configuration open class MockComponentConfiguration { diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/test/resources/execution-input/imperative-test-input.json b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/resources/execution-input/imperative-test-input.json new file mode 100644 index 000000000..d3495c456 --- /dev/null +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/test/resources/execution-input/imperative-test-input.json @@ -0,0 +1,18 @@ +{ + "commonHeader": { + "originatorId": "System", + "requestId": "1234", + "subRequestId": "1234-12234" + }, + "actionIdentifiers": { + "blueprintName": "imperative-test", + "blueprintVersion": "1.0.0", + "actionName": "imperative-test-wf", + "mode": "sync" + }, + "payload": { + "imperative-test-wf-request": { + "hostname": "localhost" + } + } +}
\ No newline at end of file |