diff options
Diffstat (limited to 'ms')
39 files changed, 1417 insertions, 377 deletions
diff --git a/ms/blueprintsprocessor/application/pom.xml b/ms/blueprintsprocessor/application/pom.xml index ed1b67dfd..0ee6ac339 100755 --- a/ms/blueprintsprocessor/application/pom.xml +++ b/ms/blueprintsprocessor/application/pom.xml @@ -24,7 +24,7 @@ <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId> <artifactId>parent</artifactId> <version>0.7.0-SNAPSHOT</version> - <relativePath>../parent</relativePath> + <relativePath>..</relativePath> </parent> <artifactId>application</artifactId> @@ -38,7 +38,6 @@ <name.space>org.onap.ccsdk.cds</name.space> <serviceArtifactName>blueprintsprocessor</serviceArtifactName> <image.name>onap/ccsdk-blueprintsprocessor</image.name> - <docker.buildArg.https_proxy>${https_proxy}</docker.buildArg.https_proxy> <docker.push.phase>deploy</docker.push.phase> <docker.verbose>true</docker.verbose> <ccsdk.project.version>${project.version}</ccsdk.project.version> @@ -141,23 +140,42 @@ <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> + <!-- BEGIN UAT --> + <dependency> + <groupId>org.skyscreamer</groupId> + <artifactId>jsonassert</artifactId> + </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>com.nhaarman.mockitokotlin2</groupId> <artifactId>mockito-kotlin</artifactId> - <version>2.1.0</version> - <scope>test</scope> + <version>2.2.0</version> </dependency> <dependency> <groupId>com.schibsted.spt.data</groupId> <artifactId>jslt</artifactId> <version>0.1.8</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>${apache.httpcomponents.client.version}</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpmime</artifactId> + <version>${apache.httpcomponents.client.version}</version> + </dependency> + <dependency> + <groupId>com.github.tomakehurst</groupId> + <artifactId>wiremock-jre8</artifactId> + <version>2.25.0</version> <scope>test</scope> </dependency> + <!-- END UAT --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> diff --git a/ms/blueprintsprocessor/application/src/main/docker/Dockerfile b/ms/blueprintsprocessor/application/src/main/docker/Dockerfile index dab0a4c01..207cec5cb 100755 --- a/ms/blueprintsprocessor/application/src/main/docker/Dockerfile +++ b/ms/blueprintsprocessor/application/src/main/docker/Dockerfile @@ -1,8 +1,5 @@ FROM omahoco1/alpine-java-python -ENV HTTP_PROXY ${HTTP_PROXY} -ENV HTTPS_PROXY ${HTTPS_PROXY} - # add entrypoint COPY run.source /etc/run.source COPY startService.sh /startService.sh @@ -15,4 +12,4 @@ RUN tar -xzf /source.tar.gz -C /tmp \ && rm -rf /source.tar.gz \ && rm -rf /tmp/@project.build.finalName@ -ENTRYPOINT /startService.sh
\ No newline at end of file +ENTRYPOINT /startService.sh diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/JsonNormalizer.kt index 69673f931..1a625c279 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/JsonNormalizer.kt @@ -17,21 +17,20 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat 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 { +internal class JsonNormalizer { companion object { - fun getNormalizer(mapper: ObjectMapper, jsltSpec: JsonNode): (String) -> String { - if (jsltSpec is MissingNode) { + fun getNormalizer(mapper: ObjectMapper, jsltSpec: JsonNode?): (String) -> String { + if (jsltSpec == null) { return { it } } return { s: String -> diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/MoreMatchers.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/MoreMatchers.kt index 71e07ab4c..163544fc9 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/MoreMatchers.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/MoreMatchers.kt @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat import com.google.common.collect.Maps import org.mockito.ArgumentMatcher diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/PathDeserializer.kt index 1a232f2d3..6b1b0c676 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/PathDeserializer.kt @@ -17,13 +17,13 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat 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) { +internal 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) diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatDefinition.kt index abb1dfcd1..3046f1041 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatDefinition.kt @@ -17,45 +17,76 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat import com.fasterxml.jackson.annotation.JsonAlias +import com.fasterxml.jackson.annotation.JsonInclude 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.fasterxml.jackson.module.kotlin.convertValue +import org.yaml.snakeyaml.DumperOptions import org.yaml.snakeyaml.Yaml -import java.nio.file.Path +import org.yaml.snakeyaml.nodes.Tag -data class ProcessDefinition(val name: String, val request: JsonNode, val expectedResponse: JsonNode, - val responseNormalizerSpec: JsonNode = MissingNode.getInstance()) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +data class ProcessDefinition(val name: String, val request: JsonNode, val expectedResponse: JsonNode? = null, + val responseNormalizerSpec: JsonNode? = null) +@JsonInclude(JsonInclude.Include.NON_EMPTY) data class RequestDefinition(val method: String, @JsonDeserialize(using = PathDeserializer::class) val path: String, val headers: Map<String, String> = emptyMap(), - val body: JsonNode = MissingNode.getInstance()) + val body: JsonNode? = null) -data class ResponseDefinition(val status: Int = 200, val body: JsonNode = MissingNode.getInstance()) { +@JsonInclude(JsonInclude.Include.NON_EMPTY) +data class ResponseDefinition(val status: Int = 200, val body: JsonNode? = null) { companion object { val DEFAULT_RESPONSE = ResponseDefinition() } } +@JsonInclude(JsonInclude.Include.NON_EMPTY) data class ExpectationDefinition(val request: RequestDefinition, val response: ResponseDefinition = ResponseDefinition.DEFAULT_RESPONSE) +@JsonInclude(JsonInclude.Include.NON_EMPTY) data class ServiceDefinition(val selector: String, val expectations: List<ExpectationDefinition>) +@JsonInclude(JsonInclude.Include.NON_EMPTY) 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) + fun dump(mapper: ObjectMapper, excludedProperties: List<String> = emptyList()): String { + val uatAsMap: Map<String, Any> = mapper.convertValue(this) + if (excludedProperties.isNotEmpty()) { + pruneTree(uatAsMap, excludedProperties) + } + return Yaml().dumpAs(uatAsMap, Tag.MAP, DumperOptions.FlowStyle.BLOCK) + } + + fun toBare(): UatDefinition { + val newProcesses = processes.map { p -> + ProcessDefinition(p.name, p.request, null, p.responseNormalizerSpec) + } + return UatDefinition(newProcesses) + } + + private fun pruneTree(node: Any?, excludedProperties: List<String>) { + when (node) { + is MutableMap<*, *> -> { + excludedProperties.forEach { key -> node.remove(key) } + node.forEach { (_, value) -> pruneTree(value, excludedProperties) } } + is List<*> -> node.forEach { value -> pruneTree(value, excludedProperties) } } } + + companion object { + fun load(mapper: ObjectMapper, spec: String): UatDefinition = + mapper.convertValue(Yaml().load(spec), UatDefinition::class.java) + + } } diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatExecutor.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatExecutor.kt new file mode 100644 index 000000000..6678075bd --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatExecutor.kt @@ -0,0 +1,324 @@ +/*- + * ============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.uat + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.argThat +import com.nhaarman.mockitokotlin2.atLeast +import com.nhaarman.mockitokotlin2.atLeastOnce +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions +import com.nhaarman.mockitokotlin2.whenever +import org.apache.http.HttpHeaders +import org.apache.http.HttpStatus +import org.apache.http.client.HttpClient +import org.apache.http.client.methods.HttpPost +import org.apache.http.entity.ContentType +import org.apache.http.entity.StringEntity +import org.apache.http.entity.mime.HttpMultipartMode +import org.apache.http.entity.mime.MultipartEntityBuilder +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.message.BasicHeader +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.notNullValue +import org.hamcrest.MatcherAssert.assertThat +import org.mockito.Answers +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.COLOR_MOCKITO +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.markerOf +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.MockInvocationLogger +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.core.env.ConfigurableEnvironment +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.util.Base64Utils +import java.util.concurrent.ConcurrentHashMap + +/** + * Assumptions: + * + * - Application HTTP service is bound to loopback interface; + * - Password is either defined in plain (with "{noop}" prefix), or it's the same of username. + * + * @author Eliezio Oliveira + */ +@Component +class UatExecutor( + private val environment: ConfigurableEnvironment, + private val restClientFactory: BluePrintRestLibPropertyService, + private val mapper: ObjectMapper +) { + + companion object { + private const val NOOP_PASSWORD_PREFIX = "{noop}" + + private val log: Logger = LoggerFactory.getLogger(UatExecutor::class.java) + private val mockLoggingListener = MockInvocationLogger(markerOf(COLOR_MOCKITO)) + } + + // use lazy evaluation to postpone until localServerPort is injected by Spring + private val baseUrl: String by lazy { + "http://127.0.0.1:${localServerPort()}" + } + + @Throws(AssertionError::class) + fun execute(uatSpec: String, cbaBytes: ByteArray) { + val uat = UatDefinition.load(mapper, uatSpec) + execute(uat, cbaBytes) + } + + /** + * + * The UAT can range from minimum to completely defined. + * + * @return an updated UAT with all NB and SB messages. + */ + @Throws(AssertionError::class) + fun execute(uat: UatDefinition, cbaBytes: ByteArray): UatDefinition { + val defaultHeaders = listOf(BasicHeader(HttpHeaders.AUTHORIZATION, clientAuthToken())) + val httpClient = HttpClientBuilder.create() + .setDefaultHeaders(defaultHeaders) + .build() + // Only if externalServices are defined + val mockInterceptor = MockPreInterceptor() + // Always defined and used, whatever the case + val spyInterceptor = SpyPostInterceptor(mapper) + restClientFactory.setInterceptors(mockInterceptor, spyInterceptor) + try { + // Configure mocked external services and save their expected requests for further validation + val requestsPerClient = uat.externalServices.associateBy( + { service -> + createRestClientMock(service.expectations).also { restClient -> + // side-effect: register restClient to override real instance + mockInterceptor.registerMock(service.selector, restClient) + } + }, + { service -> service.expectations.map { it.request } } + ) + + val newProcesses = httpClient.use { client -> + uploadBlueprint(client, cbaBytes) + + // Run processes + uat.processes.map { process -> + log.info("Executing process '${process.name}'") + val responseNormalizer = JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec) + val actualResponse = processBlueprint(client, process.request, + process.expectedResponse, responseNormalizer) + ProcessDefinition(process.name, process.request, actualResponse, process.responseNormalizerSpec) + } + } + + // Validate requests to external services + for ((mockClient, requests) in requestsPerClient) { + requests.forEach { request -> + verify(mockClient, atLeastOnce()).exchangeResource( + eq(request.method), + eq(request.path), + argThat { assertJsonEquals(request.body, this) }, + argThat(RequiredMapEntriesMatcher(request.headers))) + } + // Don't mind the invocations to the overloaded exchangeResource(String, String, String) + verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any()) + verifyNoMoreInteractions(mockClient) + } + + val newExternalServices = spyInterceptor.getSpies() + .map(SpyService::asServiceDefinition) + + return UatDefinition(newProcesses, newExternalServices) + } finally { + restClientFactory.clearInterceptors() + } + } + + private fun createRestClientMock(restExpectations: List<ExpectationDefinition>) + : BlueprintWebClientService { + val restClient = mock<BlueprintWebClientService>( + defaultAnswer = Answers.RETURNS_SMART_NULLS, + // our custom verboseLogging handler + invocationListeners = arrayOf(mockLoggingListener) + ) + + // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>) + whenever(restClient.exchangeResource(any(), any(), any())) + .thenAnswer { invocation -> + val method = invocation.arguments[0] as String + val path = invocation.arguments[1] as String + val request = invocation.arguments[2] as String + restClient.exchangeResource(method, path, request, emptyMap()) + } + for (expectation in restExpectations) { + whenever(restClient.exchangeResource( + eq(expectation.request.method), + eq(expectation.request.path), + any(), + any())) + .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString())) + } + return restClient + } + + @Throws(AssertionError::class) + private fun uploadBlueprint(client: HttpClient, cbaBytes: ByteArray) { + val multipartEntity = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addBinaryBody("file", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip") + .build() + val request = HttpPost("$baseUrl/api/v1/blueprint-model/publish").apply { + entity = multipartEntity + } + client.execute(request) { response -> + val statusLine = response.statusLine + assertThat(statusLine.statusCode, equalTo(HttpStatus.SC_OK)) + } + } + + @Throws(AssertionError::class) + private fun processBlueprint(client: HttpClient, requestBody: JsonNode, + expectedResponse: JsonNode?, responseNormalizer: (String) -> String): JsonNode { + val stringEntity = StringEntity(mapper.writeValueAsString(requestBody), ContentType.APPLICATION_JSON) + val request = HttpPost("$baseUrl/api/v1/execution-service/process").apply { + entity = stringEntity + } + val response = client.execute(request) { response -> + val statusLine = response.statusLine + assertThat(statusLine.statusCode, equalTo(HttpStatus.SC_OK)) + val entity = response.entity + assertThat("Response contains no content", entity, notNullValue()) + entity.content.bufferedReader().use { it.readText() } + } + val actualResponse = responseNormalizer(response) + if (expectedResponse != null) { + assertJsonEquals(expectedResponse, actualResponse) + } + return mapper.readTree(actualResponse)!! + } + + @Throws(AssertionError::class) + private fun assertJsonEquals(expected: JsonNode?, actual: String): Boolean { + // special case + if ((expected == null) && actual.isBlank()) { + return true + } + // general case + JSONAssert.assertEquals(expected?.toString(), actual, JSONCompareMode.LENIENT) + // assertEquals throws an exception whenever match fails + return true + } + + private fun localServerPort(): Int = + (environment.getProperty("local.server.port") + ?: environment.getRequiredProperty("blueprint.httpPort")).toInt() + + private fun clientAuthToken(): String { + val username = environment.getRequiredProperty("security.user.name") + val password = environment.getRequiredProperty("security.user.password") + val plainPassword = when { + password.startsWith(NOOP_PASSWORD_PREFIX) -> password.substring(NOOP_PASSWORD_PREFIX.length) + else -> username + } + return "Basic " + Base64Utils.encodeToString("$username:$plainPassword".toByteArray()) + } + + private class MockPreInterceptor : BluePrintRestLibPropertyService.PreInterceptor { + private val mocks = ConcurrentHashMap<String, BlueprintWebClientService>() + + override fun getInstance(jsonNode: JsonNode): BlueprintWebClientService? { + TODO("jsonNode-keyed services not yet supported") + } + + override fun getInstance(selector: String): BlueprintWebClientService? = + mocks[selector] + + fun registerMock(selector: String, client: BlueprintWebClientService) { + mocks[selector] = client + } + } + + private class SpyPostInterceptor(private val mapper: ObjectMapper) : BluePrintRestLibPropertyService.PostInterceptor { + + private val spies = ConcurrentHashMap<String, SpyService>() + + override fun getInstance(jsonNode: JsonNode, service: BlueprintWebClientService): BlueprintWebClientService { + TODO("jsonNode-keyed services not yet supported") + } + + override fun getInstance(selector: String, service: BlueprintWebClientService): BlueprintWebClientService { + val spiedService = SpyService(mapper, selector, service) + spies[selector] = spiedService + return spiedService + } + + fun getSpies(): List<SpyService> = + spies.values.toList() + } + + private class SpyService(private val mapper: ObjectMapper, + val selector: String, + private val realService: BlueprintWebClientService) : + BlueprintWebClientService by realService { + + private val expectations: MutableList<ExpectationDefinition> = mutableListOf() + + override fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> = + exchangeResource(methodType, path, request, DEFAULT_HEADERS) + + override fun exchangeResource(methodType: String, path: String, request: String, + headers: Map<String, String>): WebClientResponse<String> { + val requestDefinition = RequestDefinition(methodType, path, headers, toJson(request)) + val realAnswer = realService.exchangeResource(methodType, path, request, headers) + val responseBody = when { + // TODO: confirm if we need to normalize the response here + realAnswer.status == HttpStatus.SC_OK -> toJson(realAnswer.body) + else -> null + } + val responseDefinition = ResponseDefinition(realAnswer.status, responseBody) + expectations.add(ExpectationDefinition(requestDefinition, responseDefinition)) + return realAnswer + } + + fun asServiceDefinition() = + ServiceDefinition(selector, expectations) + + private fun toJson(str: String): JsonNode? { + return when { + str.isNotBlank() -> mapper.readTree(str) + else -> null + } + } + + companion object { + private val DEFAULT_HEADERS = mapOf( + HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE, + HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE + ) + } + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServices.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServices.kt new file mode 100644 index 000000000..f133fd7c7 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServices.kt @@ -0,0 +1,121 @@ +/*- + * ============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.uat + +import com.fasterxml.jackson.databind.ObjectMapper +import kotlinx.coroutines.runBlocking +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.COLOR_SERVICES +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.resetContextColor +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.setContextColor +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.UAT_SPECIFICATION_FILE +import org.springframework.context.annotation.Profile +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.codec.multipart.FilePart +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException +import java.io.File +import java.util.zip.ZipFile + +/** + * Supporting services to help creating UAT specifications. + * + * @author Eliezio Oliveira + */ +@RestController +@RequestMapping("/api/v1/uat") +@Profile("uat") +open class UatServices(private val uatExecutor: UatExecutor, private val mapper: ObjectMapper) { + + @PostMapping("/verify", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) + @PreAuthorize("hasRole('USER')") + @Suppress("BlockingMethodInNonBlockingContext") + open fun verify(@RequestPart("cba") cbaFile: FilePart) = runBlocking { + setContextColor(COLOR_SERVICES) + val tempFile = createTempFile() + try { + cbaFile.transferTo(tempFile) + val uatSpec = readZipEntryAsText(tempFile, UAT_SPECIFICATION_FILE) + val cbaBytes = tempFile.readBytes() + uatExecutor.execute(uatSpec, cbaBytes) + } catch (e: AssertionError) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message) + } catch (t: Throwable) { + throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, t.message, t) + } finally { + tempFile.delete() + resetContextColor() + } + } + + @PostMapping("/spy", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], produces = ["text/vnd.yaml"]) + @PreAuthorize("hasRole('USER')") + @Suppress("BlockingMethodInNonBlockingContext") + open fun spy(@RequestPart("cba") cbaFile: FilePart, + @RequestPart("uat", required = false) uatFile: FilePart?): String = runBlocking { + val tempFile = createTempFile() + setContextColor(COLOR_SERVICES) + try { + cbaFile.transferTo(tempFile) + val uatSpec = when { + uatFile != null -> uatFile.readText() + else -> readZipEntryAsText(tempFile, UAT_SPECIFICATION_FILE) + } + val uat = UatDefinition.load(mapper, uatSpec) + val cbaBytes = tempFile.readBytes() + val updatedUat = uatExecutor.execute(uat, cbaBytes) + return@runBlocking updatedUat.dump(mapper, FIELDS_TO_EXCLUDE) + } catch (t: Throwable) { + throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, t.message, t) + } finally { + tempFile.delete() + resetContextColor() + } + } + + private fun FilePart.readText(): String { + val tempFile = createTempFile() + try { + transferTo(tempFile).block() + return tempFile.readText() + } finally { + tempFile.delete() + } + } + + @Suppress("SameParameterValue") + private fun readZipEntryAsText(file: File, entryName: String): String { + return ZipFile(file).use { zipFile -> zipFile.readEntryAsText(entryName) } + } + + private fun ZipFile.readEntryAsText(entryName: String): String { + val zipEntry = getEntry(entryName) + return getInputStream(zipEntry).readBytes().toString(Charsets.UTF_8) + } + + companion object { + // Fields that can be safely ignored from BPP response, and can be omitted on the UAT specification. + private val FIELDS_TO_EXCLUDE = listOf("timestamp") + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/CollectionUtils2.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt index 63d64cae4..10139c839 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/CollectionUtils2.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt @@ -17,15 +17,8 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat.logging -import org.springframework.util.CollectionUtils -import org.springframework.util.MultiValueMap +import org.slf4j.Marker - -/** - * Convenient method to create a single-entry MultiValueMap. - */ -fun <K, V> toMultiValueMap(key: K, vararg values: V): MultiValueMap<K, V> { - return CollectionUtils.toMultiValueMap(mapOf(key to values.asList())) -} +class ColorMarker internal constructor(private val dlg: Marker) : Marker by dlg
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt new file mode 100644 index 000000000..dce516933 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt @@ -0,0 +1,45 @@ +/*- + * ============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.uat.logging + +import org.slf4j.MDC +import org.slf4j.MarkerFactory + +object LogColor { + + const val COLOR_SERVICES = "green" + const val COLOR_TEST_CLIENT = "yellow" + const val COLOR_MOCKITO = "cyan" + const val COLOR_WIREMOCK = "blue" + + // The Slf4j MDC key that will hold the global color + const val MDC_COLOR_KEY = "color" + + fun setContextColor(color: String) { + MDC.put(MDC_COLOR_KEY, color) + } + + fun resetContextColor() { + MDC.remove(MDC_COLOR_KEY) + } + + fun markerOf(color: String): ColorMarker = + ColorMarker(MarkerFactory.getMarker(color)) +} diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt new file mode 100644 index 000000000..f8e6bd486 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt @@ -0,0 +1,65 @@ +/*- + * ============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.uat.logging + +import org.mockito.listeners.InvocationListener +import org.mockito.listeners.MethodInvocationReport +import org.slf4j.LoggerFactory +import org.slf4j.Marker +import java.util.concurrent.atomic.AtomicInteger + +/** + * Logs all Mockito's mock/spy invocations. + * + * Used for debugging interactions with a mock. + */ +class MockInvocationLogger(private val marker: Marker) : InvocationListener { + + private val mockInvocationsCounter = AtomicInteger() + + override fun reportInvocation(report: MethodInvocationReport) { + val sb = StringBuilder() + sb.appendln("Method invocation #${mockInvocationsCounter.incrementAndGet()} on mock/spy") + report.locationOfStubbing?.let { location -> + sb.append(INDENT).append("stubbed ").appendln(location) + } + sb.appendln(report.invocation) + sb.append(INDENT).append("invoked ").appendln(report.invocation.location) + if (report.threwException()) { + sb.append(INDENT).append("has thrown -> ").append(report.throwable.javaClass.name) + report.throwable.message?.let { message -> + sb.append(" with message ").append(message) + } + sb.appendln() + } else { + sb.append(INDENT).append("has returned -> \"").append(report.returnedValue).append('"') + report.returnedValue?.let { value -> + sb.append(" (").append(value.javaClass.name).append(')') + } + sb.appendln() + } + log.info(marker, sb.toString()) + } + + companion object { + private const val INDENT = " " + private val log = LoggerFactory.getLogger(MockInvocationLogger::class.java) + } +} diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/SmartColorDiscriminator.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/SmartColorDiscriminator.kt new file mode 100644 index 000000000..d7b38d3fa --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/SmartColorDiscriminator.kt @@ -0,0 +1,41 @@ +/*- + * ============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.uat.logging + +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.sift.AbstractDiscriminator +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.MDC_COLOR_KEY + +class SmartColorDiscriminator : AbstractDiscriminator<ILoggingEvent>() { + var defaultValue: String = "white" + + override fun getKey(): String { + return MDC_COLOR_KEY + } + + fun setKey() { + throw UnsupportedOperationException("Key not settable. Using $MDC_COLOR_KEY") + } + + override fun getDiscriminatingValue(e: ILoggingEvent): String = + (e.marker as? ColorMarker)?.name + ?: e.mdcPropertyMap?.get(MDC_COLOR_KEY) + ?: defaultValue +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-local.yml b/ms/blueprintsprocessor/application/src/main/resources/application-local.yml new file mode 100644 index 000000000..de2cf4e52 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/resources/application-local.yml @@ -0,0 +1,65 @@ +appName: ControllerBluePrints +appVersion: 1.0.0 +blueprints: + processor: + functions: + python: + executor: + executionPath: ./components/scripts/python/ccsdk_blueprints + modulePaths: ./components/scripts/python/ccsdk_blueprints,./components/scripts/python/ccsdk_netconf,./components/scripts/python/ccsdk_restconf +blueprintsprocessor: + blueprintArchivePath: /tmp/cds/archive + blueprintDeployPath: /tmp/cds/deploy + blueprintWorkingPath: /tmp/cds/work + db: + primary: + driverClassName: org.mariadb.jdbc.Driver + hibernateDDLAuto: none + hibernateDialect: org.hibernate.dialect.MySQL5InnoDBDialect + hibernateHbm2ddlAuto: update + hibernateNamingStrategy: org.hibernate.cfg.ImprovedNamingStrategy + password: sdnctl + url: jdbc:mysql://localhost:3306/sdnctl + username: sdnctl + grpcEnable: false + grpcPort: 9111 + httpPort: 8080 + loadModelType: false + loadResourceDictionary: false + messageclient: + self-service-api: + bootstrapServers: 127.0.0.1:9092 + clientId: default-client-id + consumerTopic: receiver.t + groupId: receiver-id + kafkaEnable: false + topic: producer.t + type: kafka-basic-auth + remoteScriptCommand: + enabled: true + restclient: + sdncodl: + password: Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U + type: basic-auth + url: http://localhost:8282/ + username: admin + restconfEnabled: true +controllerblueprints: + loadInitialData: true +logging: + level: + org: + springframework: + boot: + context: + config: debug +ms_name: org.onap.ccsdk.apps.controllerblueprints +spring: + datasource: + password: sdnctl + url: jdbc:mysql://localhost:3306/sdnctl + username: sdnctl + security: + user: + name: ccsdkapps + password: '{bcrypt}$2a$10$duaUzVUVW0YPQCSIbGEkQOXwafZGwQ/b32/Ys4R1iwSSawFgz7QNu' diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-uat.yml b/ms/blueprintsprocessor/application/src/main/resources/application-uat.yml new file mode 100644 index 000000000..f00d62b0f --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/resources/application-uat.yml @@ -0,0 +1,4 @@ +server: + error: + include-exception: true + include-stacktrace: always diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt deleted file mode 100644 index ce7434f8e..000000000 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt +++ /dev/null @@ -1,242 +0,0 @@ -/*- - * ============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.MissingNode -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argThat -import com.nhaarman.mockitokotlin2.atLeast -import com.nhaarman.mockitokotlin2.atLeastOnce -import com.nhaarman.mockitokotlin2.eq -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.whenever -import org.junit.ClassRule -import org.junit.Rule -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Answers -import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse -import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.core.io.ByteArrayResource -import org.springframework.core.io.Resource -import org.springframework.http.MediaType -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 reactor.core.publisher.Mono -import java.io.File -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) -@AutoConfigureWebTestClient(timeout = "PT10S") -@ContextConfiguration(initializers = [ - WorkingFoldersInitializer::class, - TestSecuritySettings.ServerContextInitializer::class -]) -@TestPropertySource(locations = ["classpath:application-test.properties"]) -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" - const val EMBEDDED_UAT_FILE = "Tests/uat.yaml" - - @ClassRule - @JvmField - val springClassRule = SpringClassRule() - - 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 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) } - ?: emptyList() - } - } - - @Rule - @JvmField - val springMethodRule = SpringMethodRule() - - @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY, answer = Answers.RETURNS_SMART_NULLS) - lateinit var restClientFactory: BluePrintRestLibPropertyService - - @Autowired - // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)} - @Suppress("SpringJavaInjectionPointsAutowiringInspection") - lateinit var tempFolder: ExtendedTemporaryFolder - - @Autowired - lateinit var webTestClient: WebTestClient - - @Autowired - lateinit var mapper: ObjectMapper - - @BeforeTest - fun cleanupTemporaryFolder() { - tempFolder.deleteAllFiles() - } - - @Test - fun testBlueprint() { - val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE)) - - uploadBlueprint(blueprintName) - - // Configure mocked external services and save their expected requests for further validation - val requestsPerClient = uat.externalServices.associateBy( - { service -> createRestClientMock(service.selector, service.expectations) }, - { service -> service.expectations.map { it.request } } - ) - - // Run processes - for (process in uat.processes) { - log.info("Executing process '${process.name}'") - processBlueprint(process.request, process.expectedResponse, - JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec)) - } - - // Validate requests to external services - for ((mockClient, requests) in requestsPerClient) { - requests.forEach { request -> - verify(mockClient, atLeastOnce()).exchangeResource( - eq(request.method), - eq(request.path), - argThat { assertJsonEqual(request.body, this) }, - argThat(RequiredMapEntriesMatcher(request.headers))) - } - // Don't mind the invocations to the overloaded exchangeResource(String, String, String) - verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any()) - verifyNoMoreInteractions(mockClient) - } - } - - private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>) - : BlueprintWebClientService { - val restClient = mock<BlueprintWebClientService>(verboseLogging = true, - defaultAnswer = Answers.RETURNS_SMART_NULLS) - - // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>) - whenever(restClient.exchangeResource(any(), any(), any())) - .thenAnswer { invocation -> - val method = invocation.arguments[0] as String - val path = invocation.arguments[1] as String - val request = invocation.arguments[2] as String - restClient.exchangeResource(method, path, request, emptyMap()) - } - for (expectation in restExpectations) { - whenever(restClient.exchangeResource( - eq(expectation.request.method), - eq(expectation.request.path), - any(), - any())) - .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString())) - } - - whenever(restClientFactory.blueprintWebClientService(selector)) - .thenReturn(restClient) - return restClient - } - - private fun uploadBlueprint(blueprintName: String) { - val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName)) - webTestClient - .post() - .uri("/api/v1/blueprint-model/publish") - .header("Authorization", TestSecuritySettings.clientAuthToken()) - .syncBody(body) - .exchange() - .expectStatus().isOk - } - - 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.toString()), String::class.java) - .exchange() - .expectStatus().isOk - .expectBody() - .consumeWith { response -> - assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response))) - } - } - - private fun getBlueprintAsResource(blueprintName: String): Resource { - val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName) - val zipBytes = compressToBytes(baseDir) - return object : ByteArrayResource(zipBytes) { - // Filename has to be returned in order to be able to post - override fun getFilename() = "$blueprintName.zip" - } - } - - 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 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/uat/BaseUatTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/BaseUatTest.kt new file mode 100644 index 000000000..ec338f274 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/BaseUatTest.kt @@ -0,0 +1,56 @@ +/*- + * ============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.uat + +import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.COLOR_TEST_CLIENT +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.resetContextColor +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.setContextColor +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit4.SpringRunner +import kotlin.test.AfterTest +import kotlin.test.BeforeTest + +@RunWith(SpringRunner::class) +// Also set blueprintsprocessor.httpPort=0 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(initializers = [ + WorkingFoldersInitializer::class, + TestSecuritySettings.ServerContextInitializer::class +]) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +abstract class BaseUatTest { + + @BeforeTest + fun setScope() { + setContextColor(COLOR_TEST_CLIENT) + } + + @AfterTest + fun clearScope() { + resetContextColor() + } + + companion object { + const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints" + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/BlueprintsAcceptanceTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/BlueprintsAcceptanceTest.kt new file mode 100644 index 000000000..4fed0ce67 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/BlueprintsAcceptanceTest.kt @@ -0,0 +1,91 @@ +/*- + * ============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.uat + +import org.junit.ClassRule +import org.junit.Rule +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.UAT_SPECIFICATION_FILE +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.junit4.rules.SpringClassRule +import org.springframework.test.context.junit4.rules.SpringMethodRule +import java.io.File +import java.nio.file.FileSystem +import java.nio.file.FileSystems +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) +class BlueprintsAcceptanceTest(@Suppress("unused") private val blueprintName: String, // readable test description + private val rootFs: FileSystem): BaseUatTest() { + + companion object { + + @ClassRule + @JvmField + val springClassRule = SpringClassRule() + + /** + * 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 scanUatEmpoweredBlueprints(): List<Array<Any>> { + return (File(UAT_BLUEPRINTS_BASE_DIR) + .listFiles { file -> file.isDirectory && File(file, UAT_SPECIFICATION_FILE).isFile } + ?: throw RuntimeException("Failed to scan $UAT_BLUEPRINTS_BASE_DIR")) + .map { file -> + arrayOf( + file.nameWithoutExtension, + FileSystems.newFileSystem(file.canonicalFile.toPath(), null) + ) + } + } + } + + @Rule + @JvmField + val springMethodRule = SpringMethodRule() + + @Autowired + // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)} + @Suppress("SpringJavaInjectionPointsAutowiringInspection") + lateinit var tempFolder: ExtendedTemporaryFolder + + @Autowired + lateinit var uatExecutor: UatExecutor + + @BeforeTest + fun cleanupTemporaryFolder() { + tempFolder.deleteAllFiles() + } + + @Test + fun runUat() { + val uatSpec = rootFs.getPath(UAT_SPECIFICATION_FILE).toFile().readText() + val cbaBytes = compressToBytes(rootFs.getPath("/")) + uatExecutor.execute(uatSpec, cbaBytes) + } +}
\ 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/uat/ExtendedTemporaryFolder.kt index 57b4573ef..1c0067c36 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/uat/ExtendedTemporaryFolder.kt @@ -17,9 +17,8 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat -import org.junit.rules.TemporaryFolder import java.io.File import java.io.IOException import java.nio.file.* @@ -27,25 +26,27 @@ import java.nio.file.attribute.* import javax.annotation.PreDestroy class ExtendedTemporaryFolder { - private val tempFolder = TemporaryFolder() - - init { - tempFolder.create() - } + private val tempFolder = createTempDir("uat") @PreDestroy - fun delete() = tempFolder.delete() + fun delete() = tempFolder.deleteRecursively() /** * A delegate to org.junit.rules.TemporaryFolder.TemporaryFolder.newFolder(String). */ - fun newFolder(folder: String): File = tempFolder.newFolder(folder) + fun newFolder(folderName: String): File { + val dir = File(tempFolder, folderName) + if (!dir.mkdir()) { + throw IOException("Unable to create temporary directory $dir.") + } + return dir + } /** * Delete all files under the root temporary folder recursively. The folders are preserved. */ fun deleteAllFiles() { - Files.walkFileTree(tempFolder.root.toPath(), object : SimpleFileVisitor<Path>() { + Files.walkFileTree(tempFolder.toPath(), object : SimpleFileVisitor<Path>() { @Throws(IOException::class) override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult { file?.toFile()?.delete() diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/MarkedSlf4jNotifier.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/MarkedSlf4jNotifier.kt new file mode 100644 index 000000000..13ebd9e4b --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/MarkedSlf4jNotifier.kt @@ -0,0 +1,43 @@ +/*- + * ============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.uat + +import com.github.tomakehurst.wiremock.common.Notifier +import org.slf4j.LoggerFactory +import org.slf4j.Marker + +class MarkedSlf4jNotifier(private val marker: Marker) : Notifier { + + override fun info(message: String) { + log.info(marker, message) + } + + override fun error(message: String) { + log.error(marker, message) + } + + override fun error(message: String, t: Throwable) { + log.error(marker, message, t) + } + + companion object { + private val log = LoggerFactory.getLogger("uat.WireMock") + } +} diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/TestSecuritySettings.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/TestSecuritySettings.kt index f7ab2554c..216df9aef 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/TestSecuritySettings.kt +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/TestSecuritySettings.kt @@ -17,13 +17,12 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat import org.springframework.context.ApplicationContextInitializer import org.springframework.context.ConfigurableApplicationContext import org.springframework.test.context.support.TestPropertySourceUtils import org.springframework.util.Base64Utils -import java.nio.charset.StandardCharsets class TestSecuritySettings { companion object { @@ -31,7 +30,7 @@ class TestSecuritySettings { private const val authPassword = "Heisenberg" fun clientAuthToken() = - "Basic " + Base64Utils.encodeToString("$authUsername:$authPassword".toByteArray(StandardCharsets.UTF_8)) + "Basic " + Base64Utils.encodeToString("$authUsername:$authPassword".toByteArray()) } class ServerContextInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> { diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt new file mode 100644 index 000000000..78dc7099c --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt @@ -0,0 +1,260 @@ +/*- + * ============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.uat + +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder +import com.github.tomakehurst.wiremock.client.VerificationException +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.equalToJson +import com.github.tomakehurst.wiremock.client.WireMock.request +import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo +import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig +import org.apache.http.HttpStatus +import org.apache.http.client.methods.HttpPost +import org.apache.http.entity.ContentType +import org.apache.http.entity.mime.HttpMultipartMode +import org.apache.http.entity.mime.MultipartEntityBuilder +import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.message.BasicHeader +import org.hamcrest.CoreMatchers +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalToIgnoringCase +import org.jetbrains.kotlin.konan.util.prefixIfNot +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.COLOR_WIREMOCK +import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.markerOf +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.UAT_SPECIFICATION_FILE +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.web.server.LocalServerPort +import org.springframework.core.env.ConfigurableEnvironment +import org.springframework.core.env.MapPropertySource +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.support.TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME +import org.yaml.snakeyaml.Yaml +import java.nio.file.Paths +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertNotNull + +@ActiveProfiles("uat") +@Suppress("MemberVisibilityCanBePrivate") +class UatServicesTest : BaseUatTest() { + + companion object { + private const val BLUEPRINT_NAME = "pnf_config" + private val BLUEPRINT_BASE_DIR = Paths.get(UAT_BLUEPRINTS_BASE_DIR, BLUEPRINT_NAME) + private val UAT_PATH = BLUEPRINT_BASE_DIR.resolve(UAT_SPECIFICATION_FILE) + private val wireMockMarker = markerOf(COLOR_WIREMOCK) + } + + @Autowired + lateinit var mapper: ObjectMapper + + @Autowired + lateinit var environment: ConfigurableEnvironment + + private val ephemeralProperties = mutableSetOf<String>() + private val startedMockServers = mutableListOf<WireMockServer>() + + private fun setProperties(properties: Map<String, String>) { + inlinedPropertySource().putAll(properties) + ephemeralProperties += properties.keys + } + + @AfterTest + fun resetProperties() { + val source = inlinedPropertySource() + ephemeralProperties.forEach { key -> source.remove(key) } + ephemeralProperties.clear() + } + + @AfterTest + fun stopMockServers() { + startedMockServers.forEach { mockServer -> + try { + mockServer.checkForUnmatchedRequests() + } finally { + mockServer.stop() + } + } + startedMockServers.clear() + } + + private fun inlinedPropertySource(): MutableMap<String, Any> = + (environment.propertySources[INLINED_PROPERTIES_PROPERTY_SOURCE_NAME] as MapPropertySource).source + + @LocalServerPort + var localServerPort: Int = 0 + + // use lazy evaluation to postpone until localServerPort is injected by Spring + val baseUrl: String by lazy { + "http://127.0.0.1:$localServerPort" + } + + lateinit var httpClient: CloseableHttpClient + + @BeforeTest + fun setupHttpClient() { + val defaultHeaders = listOf(BasicHeader(org.apache.http.HttpHeaders.AUTHORIZATION, + TestSecuritySettings.clientAuthToken())) + httpClient = HttpClientBuilder.create() + .setDefaultHeaders(defaultHeaders) + .build() + } + + @Test + fun `verify service validates candidate UAT`() { + // GIVEN + val cbaBytes = compressToBytes(BLUEPRINT_BASE_DIR) + val multipartEntity = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addBinaryBody("cba", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip") + .build() + val request = HttpPost("$baseUrl/api/v1/uat/verify").apply { + entity = multipartEntity + } + + // WHEN + httpClient.execute(request) { response -> + + // THEN + val statusLine = response.statusLine + assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK)) + } + } + + @Test + fun `spy service generates complete UAT from bare UAT`() { + // GIVEN + val uatSpec = UAT_PATH.toFile().readText() + val fullUat = UatDefinition.load(mapper, uatSpec) + val expectedJson = mapper.writeValueAsString(fullUat) + + val bareUatBytes = fullUat.toBare().dump(mapper).toByteArray() + + fullUat.externalServices.forEach { service -> + val mockServer = createMockServer(service) + mockServer.start() + startedMockServers += mockServer + setPropertiesForMockServer(service, mockServer) + } + + val cbaBytes = compressToBytes(BLUEPRINT_BASE_DIR) + val multipartEntity = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addBinaryBody("cba", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip") + .addBinaryBody("uat", bareUatBytes, ContentType.DEFAULT_BINARY, "uat.yaml") + .build() + val request = HttpPost("$baseUrl/api/v1/uat/spy").apply { + entity = multipartEntity + } + + // WHEN + httpClient.execute(request) { response -> + + // THEN + val statusLine = response.statusLine + assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK)) + val entity = response.entity + assertNotNull(entity) + val contentType = ContentType.get(entity) + assertThat(contentType.mimeType, equalToIgnoringCase("text/vnd.yaml")) + val yamlResponse = entity.content.bufferedReader().readText() + val jsonResponse = yamlToJson(yamlResponse) + JSONAssert.assertEquals(expectedJson, jsonResponse, JSONCompareMode.LENIENT) + } + } + + private fun createMockServer(service: ServiceDefinition): WireMockServer { + val mockServer = WireMockServer(wireMockConfig() + .dynamicPort() + .notifier(MarkedSlf4jNotifier(wireMockMarker)) + ) + service.expectations.forEach { expectation -> + + val request = expectation.request + val response = expectation.response + // WebTestClient always use absolute path, prefixing with "/" if necessary + val urlPattern = urlEqualTo(request.path.prefixIfNot("/")) + val mappingBuilder: MappingBuilder = request(request.method, urlPattern) + request.headers.forEach { (key, value) -> + mappingBuilder.withHeader(key, equalTo(value)) + } + if (request.body != null) { + mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true)) + } + + val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse() + .withStatus(response.status) + if (response.body != null) { + responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body)) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + } + + mappingBuilder.willReturn(responseDefinitionBuilder) + + mockServer.stubFor(mappingBuilder) + } + return mockServer + } + + private fun setPropertiesForMockServer(service: ServiceDefinition, mockServer: WireMockServer) { + val selector = service.selector + val httpPort = mockServer.port() + val properties = mapOf( + "blueprintsprocessor.restclient.$selector.type" to "basic-auth", + "blueprintsprocessor.restclient.$selector.url" to "http://localhost:$httpPort/", + // TODO credentials should be validated + "blueprintsprocessor.restclient.$selector.username" to "admin", + "blueprintsprocessor.restclient.$selector.password" to "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U" + ) + setProperties(properties) + } + + /** + * Borrowed from com.github.tomakehurst.wiremock.junit.WireMockRule.checkForUnmatchedRequests + */ + private fun WireMockServer.checkForUnmatchedRequests() { + val unmatchedRequests = findAllUnmatchedRequests() + if (unmatchedRequests.isNotEmpty()) { + val nearMisses = findNearMissesForAllUnmatchedRequests() + if (nearMisses.isEmpty()) { + throw VerificationException.forUnmatchedRequests(unmatchedRequests) + } else { + throw VerificationException.forUnmatchedNearMisses(nearMisses) + } + } + } + + private fun yamlToJson(yaml: String): String { + val map: Map<String, Any> = Yaml().load(yaml) + return mapper.writeValueAsString(map) + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WorkingFoldersInitializer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/WorkingFoldersInitializer.kt index 37615cb1a..ab9ae31a0 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WorkingFoldersInitializer.kt +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/WorkingFoldersInitializer.kt @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ -package org.onap.ccsdk.cds.blueprintsprocessor +package org.onap.ccsdk.cds.blueprintsprocessor.uat import org.springframework.beans.factory.support.BeanDefinitionBuilder import org.springframework.beans.factory.support.BeanDefinitionRegistry diff --git a/ms/blueprintsprocessor/application/src/test/resources/logback-test.xml b/ms/blueprintsprocessor/application/src/test/resources/logback-test.xml index 70d94f5a7..f635e7925 100644 --- a/ms/blueprintsprocessor/application/src/test/resources/logback-test.xml +++ b/ms/blueprintsprocessor/application/src/test/resources/logback-test.xml @@ -16,10 +16,17 @@ --> <configuration> - <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> - <encoder> - <pattern>%d{HH:mm:ss.SSS} %-5level %-40.40logger{39} : %msg%n</pattern> - </encoder> + <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"> + <discriminator class="org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.SmartColorDiscriminator"> + <defaultValue>white</defaultValue> + </discriminator> + <sift> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%${color}(%d{HH:mm:ss.SSS} %-5level %-40.40logger{39} : %msg%n)</pattern> + </encoder> + </appender> + </sift> </appender> <logger name="org.springframework.web.HttpLogging" level="trace"/> @@ -34,8 +41,11 @@ <logger name="org.hibernate.SQL" level="debug"/> <logger name="org.hibernate.type.descriptor.sql" level="trace"/> + <logger name="org.apache.http" level="debug"/> + <logger name="org.apache.http.wire" level="error"/> + <root level="info"> - <appender-ref ref="STDOUT"/> + <appender-ref ref="SIFT"/> </root> </configuration> diff --git a/ms/blueprintsprocessor/functions/python-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutor.kt b/ms/blueprintsprocessor/functions/python-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutor.kt index 2a227ebe1..6b1f186c9 100644 --- a/ms/blueprintsprocessor/functions/python-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutor.kt +++ b/ms/blueprintsprocessor/functions/python-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutor.kt @@ -46,6 +46,7 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic const val INPUT_PACKAGES = "packages" const val DEFAULT_SELECTOR = "remote-python" + const val ATTRIBUTE_EXEC_CMD_STATUS = "status" const val ATTRIBUTE_PREPARE_ENV_LOG = "prepare-environment-logs" const val ATTRIBUTE_EXEC_CMD_LOG = "execute-command-logs" const val ATTRIBUTE_RESPONSE_DATA = "response-data" @@ -53,7 +54,7 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic override suspend fun processNB(executionRequest: ExecutionServiceInput) { - log.info("Processing : $operationInputs") + log.debug("Processing : $operationInputs") val bluePrintContext = bluePrintRuntimeService.bluePrintContext() val blueprintName = bluePrintContext.name() @@ -109,12 +110,17 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic ) val prepareEnvOutput = remoteScriptExecutionService.prepareEnv(prepareEnvInput) log.info("$ATTRIBUTE_PREPARE_ENV_LOG - ${prepareEnvOutput.response}") - setAttribute(ATTRIBUTE_PREPARE_ENV_LOG, JacksonUtils.jsonNodeFromObject(prepareEnvOutput.response)) + val logs = JacksonUtils.jsonNodeFromObject(prepareEnvOutput.response) + setAttribute(ATTRIBUTE_PREPARE_ENV_LOG, logs) setAttribute(ATTRIBUTE_EXEC_CMD_LOG, "N/A".asJsonPrimitive()) - check(prepareEnvOutput.status == StatusType.SUCCESS) { - "failed to get prepare remote env response status for requestId(${prepareEnvInput.requestId})" + + if (prepareEnvOutput.status != StatusType.SUCCESS) { + setNodeOutputErrors(prepareEnvOutput.status.name, logs) + } else { + setNodeOutputProperties(prepareEnvOutput.status.name.asJsonPrimitive(), logs, "".asJsonPrimitive()) } } + // Populate command execution properties and pass it to the remote server val properties = dynamicProperties?.returnNullIfMissing()?.rootFieldsToMap() ?: hashMapOf() @@ -124,10 +130,13 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic command = scriptCommand, properties = properties) val remoteExecutionOutput = remoteScriptExecutionService.executeCommand(remoteExecutionInput) - log.info("$ATTRIBUTE_EXEC_CMD_LOG - ${remoteExecutionOutput.response}") - setAttribute(ATTRIBUTE_EXEC_CMD_LOG, JacksonUtils.jsonNodeFromObject(remoteExecutionOutput.response)) - check(remoteExecutionOutput.status == StatusType.SUCCESS) { - "failed to get prepare remote command response status for requestId(${remoteExecutionOutput.requestId})" + + val logs = JacksonUtils.jsonNodeFromObject(remoteExecutionOutput.response) + if (remoteExecutionOutput.status != StatusType.SUCCESS) { + setNodeOutputErrors(remoteExecutionOutput.status.name,logs, remoteExecutionOutput.payload) + } else { + setNodeOutputProperties(remoteExecutionOutput.status.name.asJsonPrimitive(), logs, + remoteExecutionOutput.payload) } } catch (e: Exception) { @@ -139,7 +148,7 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { bluePrintRuntimeService.getBluePrintError() - .addError("Failed in ComponentJythonExecutor : ${runtimeException.message}") + .addError("Failed in ComponentRemotePythonExecutor : ${runtimeException.message}") } private fun formatNestedJsonNode(node: JsonNode): String { @@ -151,4 +160,27 @@ open class ComponentRemotePythonExecutor(private val remoteScriptExecutionServic } return sb.toString() } + + /** + * Utility function to set the output properties of the executor node + */ + private fun setNodeOutputProperties(status: JsonNode, message: JsonNode, artifacts: JsonNode) { + setAttribute(ATTRIBUTE_EXEC_CMD_STATUS, status) + log.info("Executor status : $status") + setAttribute(ATTRIBUTE_RESPONSE_DATA, artifacts) + log.info("Executor artifacts: $artifacts") + setAttribute(ATTRIBUTE_EXEC_CMD_LOG, message) + log.info("Executor message : $message") + } + + /** + * Utility function to set the output properties and errors of the executor node, in cas of errors + */ + private fun setNodeOutputErrors(status: String, message: JsonNode, artifacts: JsonNode = "".asJsonPrimitive() ) { + setAttribute(ATTRIBUTE_EXEC_CMD_STATUS, status.asJsonPrimitive()) + setAttribute(ATTRIBUTE_EXEC_CMD_LOG, message) + setAttribute(ATTRIBUTE_RESPONSE_DATA, artifacts) + + addError(status, ATTRIBUTE_EXEC_CMD_LOG, message.asText()) + } } diff --git a/ms/blueprintsprocessor/functions/python-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutorTest.kt b/ms/blueprintsprocessor/functions/python-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutorTest.kt index d103bbf08..89af42579 100644 --- a/ms/blueprintsprocessor/functions/python-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutorTest.kt +++ b/ms/blueprintsprocessor/functions/python-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/python/executor/ComponentRemotePythonExecutorTest.kt @@ -194,6 +194,7 @@ class MockRemoteScriptExecutionService : RemoteScriptExecutionService { assertNotNull(prepareEnvInput.packages, "failed to get packages") val remoteScriptExecutionOutput = mockk<RemoteScriptExecutionOutput>() + every { remoteScriptExecutionOutput.payload } returns "payload".asJsonPrimitive() every { remoteScriptExecutionOutput.response } returns listOf("prepared successfully") every { remoteScriptExecutionOutput.status } returns StatusType.SUCCESS return remoteScriptExecutionOutput @@ -203,6 +204,7 @@ class MockRemoteScriptExecutionService : RemoteScriptExecutionService { assertEquals(remoteExecutionInput.requestId, "123456-1000", "failed to match request id") val remoteScriptExecutionOutput = mockk<RemoteScriptExecutionOutput>() + every { remoteScriptExecutionOutput.payload } returns "payload".asJsonPrimitive() every { remoteScriptExecutionOutput.response } returns listOf("processed successfully") every { remoteScriptExecutionOutput.status } returns StatusType.SUCCESS return remoteScriptExecutionOutput diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/api/data/BlueprintRemoteProcessorData.kt b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/api/data/BlueprintRemoteProcessorData.kt index 2f9ea4a25..d63f34ced 100644 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/api/data/BlueprintRemoteProcessorData.kt +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/api/data/BlueprintRemoteProcessorData.kt @@ -39,7 +39,8 @@ data class RemoteScriptExecutionInput(var requestId: String, data class RemoteScriptExecutionOutput(var requestId: String, var response: List<String>, var status: StatusType = StatusType.SUCCESS, - var timestamp: Date = Date()) + var timestamp: Date = Date(), + var payload: JsonNode) data class PrepareRemoteEnvInput(var requestId: String, var correlationId: String? = null, diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BluePrintRestLibPropertyService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BluePrintRestLibPropertyService.kt index 9fa13bdaf..384946ae8 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BluePrintRestLibPropertyService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BluePrintRestLibPropertyService.kt @@ -29,16 +29,32 @@ import org.springframework.stereotype.Service open class BluePrintRestLibPropertyService(private var bluePrintProperties: BluePrintProperties) { - open fun blueprintWebClientService(jsonNode: JsonNode): - BlueprintWebClientService { - val restClientProperties = restClientProperties(jsonNode) - return blueprintWebClientService(restClientProperties) + private var preInterceptor: PreInterceptor? = null + private var postInterceptor: PostInterceptor? = null + + fun setInterceptors(preInterceptor: PreInterceptor?, postInterceptor: PostInterceptor?) { + this.preInterceptor = preInterceptor + this.postInterceptor = postInterceptor + } + + fun clearInterceptors() { + this.preInterceptor = null + this.postInterceptor = null + } + + open fun blueprintWebClientService(jsonNode: JsonNode): BlueprintWebClientService { + val service = preInterceptor?.getInstance(jsonNode) + ?: blueprintWebClientService(restClientProperties(jsonNode)) + return postInterceptor?.getInstance(jsonNode, service) ?: service } open fun blueprintWebClientService(selector: String): BlueprintWebClientService { - val prefix = "blueprintsprocessor.restclient.$selector" - val restClientProperties = restClientProperties(prefix) - return blueprintWebClientService(restClientProperties) + val service = preInterceptor?.getInstance(selector) ?: run { + val prefix = "blueprintsprocessor.restclient.$selector" + val restClientProperties = restClientProperties(prefix) + blueprintWebClientService(restClientProperties) + } + return postInterceptor?.getInstance(selector, service) ?: service } fun restClientProperties(prefix: String): RestClientProperties { @@ -182,6 +198,18 @@ open class BluePrintRestLibPropertyService(private var bluePrintProperties: return bluePrintProperties.propertyBeanType( prefix, PolicyManagerRestClientProperties::class.java) } + + interface PreInterceptor { + fun getInstance(jsonNode: JsonNode): BlueprintWebClientService? + + fun getInstance(selector: String): BlueprintWebClientService? + } + + interface PostInterceptor { + fun getInstance(jsonNode: JsonNode, service: BlueprintWebClientService): BlueprintWebClientService + + fun getInstance(selector: String, service: BlueprintWebClientService): BlueprintWebClientService + } } diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/RemoteScriptExecutionService.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/RemoteScriptExecutionService.kt index b0e3e4701..d6146e111 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/RemoteScriptExecutionService.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/RemoteScriptExecutionService.kt @@ -25,6 +25,7 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.* import org.onap.ccsdk.cds.blueprintsprocessor.grpc.service.BluePrintGrpcClientService import org.onap.ccsdk.cds.blueprintsprocessor.grpc.service.BluePrintGrpcLibPropertyService import org.onap.ccsdk.cds.controllerblueprints.command.api.* +import org.onap.ccsdk.cds.controllerblueprints.core.jsonAsJsonType import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory @@ -154,7 +155,8 @@ class GrpcRemoteScriptExecutionService(private val bluePrintGrpcLibPropertyServi return RemoteScriptExecutionOutput( requestId = this.requestId, response = this.responseList, - status = StatusType.valueOf(this.status.name) + status = StatusType.valueOf(this.status.name), + payload = payload.jsonAsJsonType() ) } diff --git a/ms/command-executor/src/main/docker/Dockerfile b/ms/command-executor/src/main/docker/Dockerfile index c0458bdf6..b28e580fb 100644 --- a/ms/command-executor/src/main/docker/Dockerfile +++ b/ms/command-executor/src/main/docker/Dockerfile @@ -1,8 +1,5 @@ FROM python:3.6-slim -ENV HTTP_PROXY ${HTTP_PROXY} -ENV HTTPS_PROXY ${HTTPS_PROXY} - ENV GRPC_PYTHON_VERSION 1.20.0 RUN python -m pip install --upgrade pip RUN pip install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION} @@ -21,4 +18,4 @@ RUN tar -xzf /source.tar.gz -C /tmp \ VOLUME /opt/app/onap/blueprints/deploy/ -ENTRYPOINT /opt/app/onap/start.sh
\ No newline at end of file +ENTRYPOINT /opt/app/onap/start.sh diff --git a/ms/command-executor/src/main/python/cds_utils/__init__.py b/ms/command-executor/src/main/python/cds_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ms/command-executor/src/main/python/cds_utils/__init__.py diff --git a/ms/command-executor/src/main/python/cds_utils/payload_coder.py b/ms/command-executor/src/main/python/cds_utils/payload_coder.py new file mode 100644 index 000000000..951262172 --- /dev/null +++ b/ms/command-executor/src/main/python/cds_utils/payload_coder.py @@ -0,0 +1,13 @@ +import json
+from email.mime import multipart
+from email.mime import text
+import email.parser
+
+def send_response_data_payload(json_payload):
+ m = multipart.MIMEMultipart("form-data")
+ data = text.MIMEText("response_payload", "json", "utf8")
+ data.set_payload(json.JSONEncoder().encode(json_payload))
+ m.attach(data)
+ print("BEGIN_EXTRA_PAYLOAD")
+ print(m.as_string())
+ print("END_EXTRA_PAYLOAD")
\ No newline at end of file diff --git a/ms/command-executor/src/main/python/command_executor_handler.py b/ms/command-executor/src/main/python/command_executor_handler.py index 972dad627..c920dda89 100644 --- a/ms/command-executor/src/main/python/command_executor_handler.py +++ b/ms/command-executor/src/main/python/command_executor_handler.py @@ -25,6 +25,8 @@ import virtualenv import venv import utils import proto.CommandExecutor_pb2 as CommandExecutor_pb2 +import email.parser +import json REQUIREMENTS_TXT = "requirements.txt" @@ -75,6 +77,10 @@ class CommandExecutorHandler(): else: cmd = cmd + "; " + request.command + " " + re.escape(MessageToJson(request.properties)) + payload_result = {} + payload_section = [] + is_payload_section = False + try: with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, bufsize=1, universal_newlines=True) as newProcess: @@ -82,19 +88,36 @@ class CommandExecutorHandler(): output = newProcess.stdout.readline() if output == '' and newProcess.poll() is not None: break - if output: + if output.startswith('BEGIN_EXTRA_PAYLOAD'): + is_payload_section = True + output = newProcess.stdout.readline() + if output.startswith('END_EXTRA_PAYLOAD'): + is_payload_section = False + output = '' + payload = '\n'.join(payload_section) + msg = email.parser.Parser().parsestr(payload) + for part in msg.get_payload(): + payload_result = json.loads(part.get_payload()) + if output and not is_payload_section: self.logger.info(output.strip()) results.append(output.strip()) - rc = newProcess.poll() + else: + payload_section.append(output.strip()) + rc = newProcess.poll() except Exception as e: self.logger.info("{} - Failed to execute command. Error: {}".format(self.blueprint_id, e)) results.append(e) - return False + payload_result["cds_return_code"] = False + return payload_result # deactivate_venv(blueprint_id) - return True + + payload_result["cds_return_code"] = rc + return payload_result def install_packages(self, request, type, f, results): + success = self.install_python_packages('UTILITY', results) + for package in request.packages: if package.type == type: f.write("Installed %s packages:\r\n" % CommandExecutor_pb2.PackageType.Name(type)) @@ -116,6 +139,8 @@ class CommandExecutorHandler(): if REQUIREMENTS_TXT == package: command = ["pip", "install", "-r", self.venv_home + "/Environments/" + REQUIREMENTS_TXT] + elif package == 'UTILITY': + command = ["cp", "-r", "./cds_utils", self.venv_home + "/lib/python3.6/site-packages/"] else: command = ["pip", "install", package] diff --git a/ms/command-executor/src/main/python/command_executor_server.py b/ms/command-executor/src/main/python/command_executor_server.py index 6266141d9..577c8a0ca 100644 --- a/ms/command-executor/src/main/python/command_executor_server.py +++ b/ms/command-executor/src/main/python/command_executor_server.py @@ -16,7 +16,7 @@ # limitations under the License. # import logging - +import os, sys import proto.CommandExecutor_pb2_grpc as CommandExecutor_pb2_grpc from command_executor_handler import CommandExecutorHandler @@ -39,19 +39,26 @@ class CommandExecutorServer(CommandExecutor_pb2_grpc.CommandExecutorServiceServi handler = CommandExecutorHandler(request) if not handler.prepare_env(request, results): self.logger.info("{} - Failed to prepare python environment. {}".format(blueprint_id, results)) - return utils.build_response(request, results, False) + return utils.build_response(request, results, {}, False) self.logger.info("{} - Package installation logs {}".format(blueprint_id, results)) - return utils.build_response(request, results) + return utils.build_response(request, results, {}, True) def executeCommand(self, request, context): blueprint_id = utils.get_blueprint_id(request) self.logger.info("{} - Received executeCommand request".format(blueprint_id)) - self.logger.info(request) + if os.environ.get('CE_DEBUG','false') == "true": + self.logger.info(request) - results = [] + log_results = [] + payload_result = {} handler = CommandExecutorHandler(request) - if not handler.execute_command(request, results): - self.logger.info("{} - Failed to executeCommand. {}".format(blueprint_id, results)) - return utils.build_response(request, results, False) - self.logger.info("{} - Execution finished successfully.".format(blueprint_id)) - return utils.build_response(request, results) + payload_result = handler.execute_command(request, log_results) + if not payload_result["cds_return_code"]: + self.logger.info("{} - Failed to executeCommand. {}".format(blueprint_id, log_results)) + else: + self.logger.info("{} - Execution finished successfully.".format(blueprint_id)) + + ret = utils.build_response(request, log_results, payload_result, payload_result["cds_return_code"]) + self.logger.info("Payload returned %s" % payload_result) + + return ret
\ No newline at end of file diff --git a/ms/command-executor/src/main/python/proto/CommandExecutor_pb2.py b/ms/command-executor/src/main/python/proto/CommandExecutor_pb2.py index 478e00959..4edfc6cd5 100644 --- a/ms/command-executor/src/main/python/proto/CommandExecutor_pb2.py +++ b/ms/command-executor/src/main/python/proto/CommandExecutor_pb2.py @@ -23,7 +23,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='org.onap.ccsdk.cds.controllerblueprints.command.api', syntax='proto3', serialized_options=_b('P\001'), - serialized_pb=_b('\n\x15\x43ommandExecutor.proto\x12\x33org.onap.ccsdk.cds.controllerblueprints.command.api\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8f\x02\n\x0e\x45xecutionInput\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x15\n\rcorrelationId\x18\x02 \x01(\t\x12U\n\x0bidentifiers\x18\x03 \x01(\x0b\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.Identifiers\x12\x0f\n\x07\x63ommand\x18\x04 \x01(\t\x12\x0f\n\x07timeOut\x18\x05 \x01(\x05\x12+\n\nproperties\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xd0\x02\n\x0fPrepareEnvInput\x12U\n\x0bidentifiers\x18\x01 \x01(\x0b\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.Identifiers\x12\x11\n\trequestId\x18\x02 \x01(\t\x12\x15\n\rcorrelationId\x18\x03 \x01(\t\x12O\n\x08packages\x18\x04 \x03(\x0b\x32=.org.onap.ccsdk.cds.controllerblueprints.command.api.Packages\x12\x0f\n\x07timeOut\x18\x05 \x01(\x05\x12+\n\nproperties\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\">\n\x0bIdentifiers\x12\x15\n\rblueprintName\x18\x01 \x01(\t\x12\x18\n\x10\x62lueprintVersion\x18\x02 \x01(\t\"\xba\x01\n\x0f\x45xecutionOutput\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x03(\t\x12S\n\x06status\x18\x03 \x01(\x0e\x32\x43.org.onap.ccsdk.cds.controllerblueprints.command.api.ResponseStatus\x12-\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"k\n\x08Packages\x12N\n\x04type\x18\x01 \x01(\x0e\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.PackageType\x12\x0f\n\x07package\x18\x02 \x03(\t**\n\x0eResponseStatus\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01**\n\x0bPackageType\x12\x07\n\x03pip\x10\x00\x12\x12\n\x0e\x61nsible_galaxy\x10\x01\x32\xd1\x02\n\x16\x43ommandExecutorService\x12\x98\x01\n\nprepareEnv\x12\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.PrepareEnvInput\x1a\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutput\x12\x9b\x01\n\x0e\x65xecuteCommand\x12\x43.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionInput\x1a\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutputB\x02P\x01\x62\x06proto3') + serialized_pb=_b('\n\x15\x43ommandExecutor.proto\x12\x33org.onap.ccsdk.cds.controllerblueprints.command.api\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8f\x02\n\x0e\x45xecutionInput\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x15\n\rcorrelationId\x18\x02 \x01(\t\x12U\n\x0bidentifiers\x18\x03 \x01(\x0b\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.Identifiers\x12\x0f\n\x07\x63ommand\x18\x04 \x01(\t\x12\x0f\n\x07timeOut\x18\x05 \x01(\x05\x12+\n\nproperties\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xd0\x02\n\x0fPrepareEnvInput\x12U\n\x0bidentifiers\x18\x01 \x01(\x0b\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.Identifiers\x12\x11\n\trequestId\x18\x02 \x01(\t\x12\x15\n\rcorrelationId\x18\x03 \x01(\t\x12O\n\x08packages\x18\x04 \x03(\x0b\x32=.org.onap.ccsdk.cds.controllerblueprints.command.api.Packages\x12\x0f\n\x07timeOut\x18\x05 \x01(\x05\x12+\n\nproperties\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\">\n\x0bIdentifiers\x12\x15\n\rblueprintName\x18\x01 \x01(\t\x12\x18\n\x10\x62lueprintVersion\x18\x02 \x01(\t\"\xcb\x01\n\x0f\x45xecutionOutput\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x03(\t\x12S\n\x06status\x18\x03 \x01(\x0e\x32\x43.org.onap.ccsdk.cds.controllerblueprints.command.api.ResponseStatus\x12-\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07payload\x18\x05 \x01(\t\"k\n\x08Packages\x12N\n\x04type\x18\x01 \x01(\x0e\x32@.org.onap.ccsdk.cds.controllerblueprints.command.api.PackageType\x12\x0f\n\x07package\x18\x02 \x03(\t**\n\x0eResponseStatus\x12\x0b\n\x07SUCCESS\x10\x00\x12\x0b\n\x07\x46\x41ILURE\x10\x01*9\n\x0bPackageType\x12\x07\n\x03pip\x10\x00\x12\x12\n\x0e\x61nsible_galaxy\x10\x01\x12\r\n\tutilities\x10\x02\x32\xd1\x02\n\x16\x43ommandExecutorService\x12\x98\x01\n\nprepareEnv\x12\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.PrepareEnvInput\x1a\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutput\x12\x9b\x01\n\x0e\x65xecuteCommand\x12\x43.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionInput\x1a\x44.org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutputB\x02P\x01\x62\x06proto3') , dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) @@ -44,8 +44,8 @@ _RESPONSESTATUS = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=1116, - serialized_end=1158, + serialized_start=1133, + serialized_end=1175, ) _sym_db.RegisterEnumDescriptor(_RESPONSESTATUS) @@ -64,11 +64,15 @@ _PACKAGETYPE = _descriptor.EnumDescriptor( name='ansible_galaxy', index=1, number=1, serialized_options=None, type=None), + _descriptor.EnumValueDescriptor( + name='utilities', index=2, number=2, + serialized_options=None, + type=None), ], containing_type=None, serialized_options=None, - serialized_start=1160, - serialized_end=1202, + serialized_start=1177, + serialized_end=1234, ) _sym_db.RegisterEnumDescriptor(_PACKAGETYPE) @@ -77,6 +81,7 @@ SUCCESS = 0 FAILURE = 1 pip = 0 ansible_galaxy = 1 +utilities = 2 @@ -299,6 +304,13 @@ _EXECUTIONOUTPUT = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='payload', full_name='org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutput.payload', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -312,7 +324,7 @@ _EXECUTIONOUTPUT = _descriptor.Descriptor( oneofs=[ ], serialized_start=819, - serialized_end=1005, + serialized_end=1022, ) @@ -349,8 +361,8 @@ _PACKAGES = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1007, - serialized_end=1114, + serialized_start=1024, + serialized_end=1131, ) _EXECUTIONINPUT.fields_by_name['identifiers'].message_type = _IDENTIFIERS @@ -372,39 +384,39 @@ DESCRIPTOR.enum_types_by_name['ResponseStatus'] = _RESPONSESTATUS DESCRIPTOR.enum_types_by_name['PackageType'] = _PACKAGETYPE _sym_db.RegisterFileDescriptor(DESCRIPTOR) -ExecutionInput = _reflection.GeneratedProtocolMessageType('ExecutionInput', (_message.Message,), dict( - DESCRIPTOR = _EXECUTIONINPUT, - __module__ = 'CommandExecutor_pb2' +ExecutionInput = _reflection.GeneratedProtocolMessageType('ExecutionInput', (_message.Message,), { + 'DESCRIPTOR' : _EXECUTIONINPUT, + '__module__' : 'CommandExecutor_pb2' # @@protoc_insertion_point(class_scope:org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionInput) - )) + }) _sym_db.RegisterMessage(ExecutionInput) -PrepareEnvInput = _reflection.GeneratedProtocolMessageType('PrepareEnvInput', (_message.Message,), dict( - DESCRIPTOR = _PREPAREENVINPUT, - __module__ = 'CommandExecutor_pb2' +PrepareEnvInput = _reflection.GeneratedProtocolMessageType('PrepareEnvInput', (_message.Message,), { + 'DESCRIPTOR' : _PREPAREENVINPUT, + '__module__' : 'CommandExecutor_pb2' # @@protoc_insertion_point(class_scope:org.onap.ccsdk.cds.controllerblueprints.command.api.PrepareEnvInput) - )) + }) _sym_db.RegisterMessage(PrepareEnvInput) -Identifiers = _reflection.GeneratedProtocolMessageType('Identifiers', (_message.Message,), dict( - DESCRIPTOR = _IDENTIFIERS, - __module__ = 'CommandExecutor_pb2' +Identifiers = _reflection.GeneratedProtocolMessageType('Identifiers', (_message.Message,), { + 'DESCRIPTOR' : _IDENTIFIERS, + '__module__' : 'CommandExecutor_pb2' # @@protoc_insertion_point(class_scope:org.onap.ccsdk.cds.controllerblueprints.command.api.Identifiers) - )) + }) _sym_db.RegisterMessage(Identifiers) -ExecutionOutput = _reflection.GeneratedProtocolMessageType('ExecutionOutput', (_message.Message,), dict( - DESCRIPTOR = _EXECUTIONOUTPUT, - __module__ = 'CommandExecutor_pb2' +ExecutionOutput = _reflection.GeneratedProtocolMessageType('ExecutionOutput', (_message.Message,), { + 'DESCRIPTOR' : _EXECUTIONOUTPUT, + '__module__' : 'CommandExecutor_pb2' # @@protoc_insertion_point(class_scope:org.onap.ccsdk.cds.controllerblueprints.command.api.ExecutionOutput) - )) + }) _sym_db.RegisterMessage(ExecutionOutput) -Packages = _reflection.GeneratedProtocolMessageType('Packages', (_message.Message,), dict( - DESCRIPTOR = _PACKAGES, - __module__ = 'CommandExecutor_pb2' +Packages = _reflection.GeneratedProtocolMessageType('Packages', (_message.Message,), { + 'DESCRIPTOR' : _PACKAGES, + '__module__' : 'CommandExecutor_pb2' # @@protoc_insertion_point(class_scope:org.onap.ccsdk.cds.controllerblueprints.command.api.Packages) - )) + }) _sym_db.RegisterMessage(Packages) @@ -416,8 +428,8 @@ _COMMANDEXECUTORSERVICE = _descriptor.ServiceDescriptor( file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=1205, - serialized_end=1542, + serialized_start=1237, + serialized_end=1574, methods=[ _descriptor.MethodDescriptor( name='prepareEnv', diff --git a/ms/command-executor/src/main/python/server.py b/ms/command-executor/src/main/python/server.py index 453d751b2..c136bd8cc 100644 --- a/ms/command-executor/src/main/python/server.py +++ b/ms/command-executor/src/main/python/server.py @@ -43,7 +43,7 @@ def serve(): 'Access denied!') server = grpc.server( - futures.ThreadPoolExecutor(max_workers=10), + futures.ThreadPoolExecutor(max_workers=15), interceptors=(header_validator,)) CommandExecutor_pb2_grpc.add_CommandExecutorServiceServicer_to_server( diff --git a/ms/command-executor/src/main/python/utils.py b/ms/command-executor/src/main/python/utils.py index 4314b287d..a3748eb17 100644 --- a/ms/command-executor/src/main/python/utils.py +++ b/ms/command-executor/src/main/python/utils.py @@ -16,7 +16,7 @@ from google.protobuf.timestamp_pb2 import Timestamp import proto.CommandExecutor_pb2 as CommandExecutor_pb2 - +import json def get_blueprint_id(request): blueprint_name = request.identifiers.blueprintName @@ -24,7 +24,7 @@ def get_blueprint_id(request): return blueprint_name + '/' + blueprint_version -def build_response(request, results, is_success=True): +def build_response(request, log_results, payload_return, is_success=False): if is_success: status = CommandExecutor_pb2.SUCCESS else: @@ -32,5 +32,9 @@ def build_response(request, results, is_success=True): timestamp = Timestamp() timestamp.GetCurrentTime() - return CommandExecutor_pb2.ExecutionOutput(requestId=request.requestId, response=results, status=status, - timestamp=timestamp) + + if 'cds_return_code' in payload_return: + payload_return.pop('cds_return_code') + payload_str = json.dumps(payload_return) + return CommandExecutor_pb2.ExecutionOutput(requestId=request.requestId, response=log_results, status=status, + payload=payload_str, timestamp=timestamp) diff --git a/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt index 6a616cefd..509b8ca3a 100644 --- a/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt +++ b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt @@ -164,6 +164,8 @@ object BluePrintConstants { const val TOSCA_SCRIPTS_KOTLIN_DIR: String = "$TOSCA_SCRIPTS_DIR/kotlin" const val TOSCA_SCRIPTS_JYTHON_DIR: String = "$TOSCA_SCRIPTS_DIR/python" + const val UAT_SPECIFICATION_FILE = "Tests/uat.yaml" + const val GRAPH_START_NODE_NAME = "START" const val GRAPH_END_NODE_NAME = "END" diff --git a/ms/py-executor/docker/Dockerfile b/ms/py-executor/docker/Dockerfile index b49daf68f..9e86cc87e 100644 --- a/ms/py-executor/docker/Dockerfile +++ b/ms/py-executor/docker/Dockerfile @@ -1,8 +1,5 @@ FROM python:3.7-slim -ENV HTTP_PROXY ${HTTP_PROXY} -ENV HTTPS_PROXY ${HTTPS_PROXY} - RUN mkdir -p /opt/app/onap/logs/ && touch /opt/app/onap/logs/application.log COPY @project.build.finalName@-@assembly.id@.tar.gz /source.tar.gz @@ -15,4 +12,4 @@ RUN pip install --no-cache-dir -r /opt/app/onap/python/requirements.txt VOLUME /opt/app/onap/blueprints/deploy/ -ENTRYPOINT /opt/app/onap/python/start.sh
\ No newline at end of file +ENTRYPOINT /opt/app/onap/python/start.sh diff --git a/ms/sdclistener/distribution/src/main/docker/Dockerfile b/ms/sdclistener/distribution/src/main/docker/Dockerfile index e945188a8..34f6443bf 100755 --- a/ms/sdclistener/distribution/src/main/docker/Dockerfile +++ b/ms/sdclistener/distribution/src/main/docker/Dockerfile @@ -1,8 +1,5 @@ FROM openjdk:8-jdk-alpine -ENV HTTP_PROXY ${HTTP_PROXY} -ENV HTTPS_PROXY ${HTTPS_PROXY} - # add entrypoint COPY startService.sh /startService.sh RUN chmod 751 /startService.sh |