From b134a815fbb3404e46551f8f2361e6cca6c7728d Mon Sep 17 00:00:00 2001 From: prathameshmo Date: Tue, 11 Jun 2019 17:47:42 -0400 Subject: [PATCH] Kafka Messaging Controller API. Things done- Addressed review comments. Logic to consume events and process it. Added integration testing. Change-Id: If574a363f9fb8581018cc5a7ba106251a9d8caf1 Issue-ID:CCSDK-1356 Signed-off-by: prathamesh morde Signed-off-by: prathameshmo --- .../BlueprintProcessorApplication.java | 3 +- .../src/main/resources/application-dev.properties | 10 + .../src/main/resources/application.properties | 8 + .../BlueprintProcessorApplicationTest.java | 2 - .../src/test/resources/application.properties | 10 + .../KafkaBasicAuthMessageProducerService.kt | 5 +- .../modules/inbounds/selfservice-api/pom.xml | 25 +++ .../selfservice/api/MessagingConfig.kt | 47 +++++ .../selfservice/api/MessagingController.kt | 74 ++++++++ .../api/BluePrintManagementGRPCHandlerTest.kt | 7 +- .../api/BluePrintProcessingGRPCHandlerTest.kt | 4 +- .../selfservice/api/ExecutionServiceHandlerTest.kt | 4 +- .../api/messaginglib/MessagingControllerTest.kt | 211 +++++++++++++++++++++ .../api/messaginglib/ProducerConfiguration.kt | 48 +++++ .../src/test/resources/application-test.properties | 9 + .../test/resources/cba-for-kafka-integration.zip | Bin 0 -> 20700 bytes ms/blueprintsprocessor/parent/pom.xml | 7 + 17 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingConfig.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingController.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/MessagingControllerTest.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/ProducerConfiguration.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/cba-for-kafka-integration.zip diff --git a/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.java b/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.java index 2b6f8bcf1..c6400db35 100644 --- a/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.java +++ b/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.java @@ -29,8 +29,7 @@ import org.springframework.context.annotation.ComponentScan; */ @SpringBootApplication @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) -@ComponentScan(basePackages = {"org.onap.ccsdk.cds.controllerblueprints", - "org.onap.ccsdk.cds.blueprintsprocessor"}) +@ComponentScan(basePackages = {"org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"}) public class BlueprintProcessorApplication { public static void main(String[] args) { diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties index a94fdf390..fae1adb72 100755 --- a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties +++ b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties @@ -1,3 +1,4 @@ +<<<<<<< HEAD # # Copyright � 2017-2018 AT&T Intellectual Property. # @@ -73,3 +74,12 @@ blueprintsprocessor.cliExecutor.enabled=true ### If enabling remote python executor, set this value to true ### blueprintprocessor.remoteScriptCommand.enabled=true blueprintprocessor.remoteScriptCommand.enabled=false + +# Kafka-message-lib Configurations +blueprintsprocessor.messageclient.self-service-api.topic=producer.t +blueprintsprocessor.messageclient.self-service-api.type=kafka-basic-auth +blueprintsprocessor.messageclient.self-service-api.bootstrapServers=127.0.0.1:9092 +blueprintsprocessor.messageclient.self-service-api.consumerTopic=receiver.t +blueprintsprocessor.messageclient.self-service-api.groupId=receiver-id +blueprintsprocessor.messageclient.self-service-api.clientId=default-client-id +blueprintsprocessor.messageclient.self-service-api.kafkaEnable=false diff --git a/ms/blueprintsprocessor/application/src/main/resources/application.properties b/ms/blueprintsprocessor/application/src/main/resources/application.properties index 1319d9fb5..d6e7dc890 100755 --- a/ms/blueprintsprocessor/application/src/main/resources/application.properties +++ b/ms/blueprintsprocessor/application/src/main/resources/application.properties @@ -72,3 +72,11 @@ blueprintsprocessor.restclient.primary-aai-data.url=https://aai.onap:8443 blueprintsprocessor.restclient.primary-aai-data.username=aai@aai.onap.org blueprintsprocessor.restclient.primary-aai-data.password=demo123456! +# Kafka-message-lib Configuration +blueprintsprocessor.messageclient.self-service-api.topic=producer.t +blueprintsprocessor.messageclient.self-service-api.type=kafka-basic-auth +blueprintsprocessor.messageclient.self-service-api.bootstrapServers=127.0.0.1:9092 +blueprintsprocessor.messageclient.self-service-api.consumerTopic=receiver.t +blueprintsprocessor.messageclient.self-service-api.groupId=receiver-id +blueprintsprocessor.messageclient.self-service-api.clientId=default-client-id +blueprintsprocessor.messageclient.self-service-api.kafkaEnable=false diff --git a/ms/blueprintsprocessor/application/src/test/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplicationTest.java b/ms/blueprintsprocessor/application/src/test/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplicationTest.java index 90783d40f..fc36e6287 100644 --- a/ms/blueprintsprocessor/application/src/test/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplicationTest.java +++ b/ms/blueprintsprocessor/application/src/test/java/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplicationTest.java @@ -16,7 +16,6 @@ package org.onap.ccsdk.cds.blueprintsprocessor; - import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -34,7 +33,6 @@ import org.springframework.test.context.junit4.SpringRunner; * @author Brinda Santh * DATE : 8/14/2018 */ - @RunWith(SpringRunner.class) @ContextConfiguration(classes = {BlueprintProcessorApplication.class, BluePrintLoadConfiguration.class}) @SpringBootTest(classes = BlueprintProcessorApplication.class, diff --git a/ms/blueprintsprocessor/application/src/test/resources/application.properties b/ms/blueprintsprocessor/application/src/test/resources/application.properties index 09ee651a4..ea2f976a3 100644 --- a/ms/blueprintsprocessor/application/src/test/resources/application.properties +++ b/ms/blueprintsprocessor/application/src/test/resources/application.properties @@ -46,3 +46,13 @@ blueprintprocessor.netconfExecutor.enabled=true blueprintprocessor.restConfExecutor.enabled=true blueprintsprocessor.cliExecutor.enabled=true blueprintprocessor.remoteScriptCommand.enabled=false + + +# Kafka-message-lib Configuration +blueprintsprocessor.messageclient.self-service-api.topic=producer.t +blueprintsprocessor.messageclient.self-service-api.type=kafka-basic-auth +blueprintsprocessor.messageclient.self-service-api.bootstrapServers=127.0.0.1:9092 +blueprintsprocessor.messageclient.self-service-api.consumerTopic=receiver.t +blueprintsprocessor.messageclient.self-service-api.groupId=receiver-id +blueprintsprocessor.messageclient.self-service-api.clientId=default-client-id +blueprintsprocessor.messageclient.self-service-api.kafkaEnable=false diff --git a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageProducerService.kt b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageProducerService.kt index 52ac346db..008e92437 100644 --- a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageProducerService.kt +++ b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageProducerService.kt @@ -16,6 +16,7 @@ package org.onap.ccsdk.cds.blueprintsprocessor.message.service +import org.apache.commons.lang.builder.ToStringBuilder import org.apache.kafka.clients.producer.ProducerConfig.* import org.apache.kafka.common.serialization.StringSerializer import org.onap.ccsdk.cds.blueprintsprocessor.message.KafkaBasicAuthMessageProducerProperties @@ -27,7 +28,6 @@ import org.springframework.kafka.core.ProducerFactory import org.springframework.kafka.support.SendResult import org.springframework.util.concurrent.ListenableFutureCallback - class KafkaBasicAuthMessageProducerService( private val messageProducerProperties: KafkaBasicAuthMessageProducerProperties) : BlueprintMessageProducerService { @@ -64,9 +64,8 @@ class KafkaBasicAuthMessageProducerService( return true } - private fun producerFactory(additionalConfig: Map? = null): ProducerFactory { - log.info("Client Properties : $messageProducerProperties") + log.info("Client Properties : ${ToStringBuilder.reflectionToString(messageProducerProperties)}") val configProps = hashMapOf() configProps[BOOTSTRAP_SERVERS_CONFIG] = messageProducerProperties.bootstrapServers configProps[KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/pom.xml b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/pom.xml index 340f2c618..89ad720f6 100755 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/pom.xml +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/pom.xml @@ -42,6 +42,7 @@ proto-definition ${project.version} + org.onap.ccsdk.cds.controllerblueprints blueprint-core @@ -59,6 +60,30 @@ h2 test + + + + org.onap.ccsdk.cds.blueprintsprocessor + message-lib + + + + + org.springframework.kafka + spring-kafka + + + org.springframework.kafka + spring-kafka-test + test + + + + + org.apache.kafka + kafka_2.11 + ${kafka.version} + diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingConfig.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingConfig.kt new file mode 100644 index 000000000..a04a79921 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingConfig.kt @@ -0,0 +1,47 @@ +package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api + +import org.apache.kafka.clients.CommonClientConfigs +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.kafka.annotation.EnableKafka +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.support.serializer.JsonDeserializer + +@Configuration +open class MessagingConfig { + + @Value("\${blueprintsprocessor.messageclient.self-service-api.groupId}") + lateinit var groupId: String + + @Value("\${blueprintsprocessor.messageclient.self-service-api.bootstrapServers}") + lateinit var bootstrapServers: String + + open fun consumerFactory(): ConsumerFactory? { + val configProperties = hashMapOf() + configProperties[CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + configProperties[ConsumerConfig.GROUP_ID_CONFIG] = groupId + configProperties[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name + configProperties[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java.name + configProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest") + + return DefaultKafkaConsumerFactory(configProperties, StringDeserializer(), JsonDeserializer(ExecutionServiceInput::class.java)) + } + + /** + * Creation of a Kafka MessageListener Container + * + * @return KafkaListener instance. + */ + @Bean + open fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory { + val factory = ConcurrentKafkaListenerContainerFactory() + factory.consumerFactory = consumerFactory() + return factory + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingController.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingController.kt new file mode 100644 index 000000000..1d219a83e --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/MessagingController.kt @@ -0,0 +1,74 @@ +/* + * Copyright © 2019 Bell Canada + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api + +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.apache.commons.lang3.builder.ToStringBuilder +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BluePrintMessageLibPropertyService +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.stereotype.Service + +@ConditionalOnProperty(name = ["blueprintsprocessor.messageclient.self-service-api.kafkaEnable"], havingValue = "true") +@Service +open class MessagingController(private val propertyService: BluePrintMessageLibPropertyService, + private val executionServiceHandler: ExecutionServiceHandler) { + + private val log = LoggerFactory.getLogger(MessagingController::class.java)!! + + companion object { + // TODO PREFIX should be retrieved from model or from request. + const val PREFIX = "self-service-api" + const val EXECUTION_STATUS = 200 + } + + @KafkaListener(topics = ["\${blueprintsprocessor.messageclient.self-service-api.consumerTopic}"]) + open fun receive(input: ExecutionServiceInput) { + + log.info("Successfully received a message: {}", ToStringBuilder.reflectionToString(input)) + + runBlocking { + log.info("Successfully received a message: {}", ToStringBuilder.reflectionToString(input)) + + // Process the message. + async { + processMessage(input) + } + } + } + + private suspend fun processMessage(executionServiceInput: ExecutionServiceInput) { + + val executionServiceOutput = executionServiceHandler.doProcess(executionServiceInput) + + if (executionServiceOutput.status.code == EXECUTION_STATUS) { + val bluePrintMessageClientService = propertyService + .blueprintMessageClientService(PREFIX) + + val payload = executionServiceOutput.payload + + log.info("The payload to publish is {}", payload) + + bluePrintMessageClientService.sendMessage(payload) + } + else { + log.error("Fail to process the given event due to {}", executionServiceOutput.status.errorMessage) + } + } +} diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintManagementGRPCHandlerTest.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintManagementGRPCHandlerTest.kt index fd764d78f..e084c60cf 100644 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintManagementGRPCHandlerTest.kt +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintManagementGRPCHandlerTest.kt @@ -23,6 +23,8 @@ import io.grpc.testing.GrpcServerRule import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.messaginglib.MessagingControllerTest +import org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.messaginglib.ProducerConfiguration import org.onap.ccsdk.cds.controllerblueprints.common.api.CommonHeader import org.onap.ccsdk.cds.controllerblueprints.core.deleteDir import org.onap.ccsdk.cds.controllerblueprints.core.normalizedFile @@ -33,6 +35,7 @@ import org.onap.ccsdk.cds.controllerblueprints.management.api.FileChunk import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit4.SpringRunner @@ -44,7 +47,9 @@ import kotlin.test.assertTrue @RunWith(SpringRunner::class) @EnableAutoConfiguration @DirtiesContext -@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"], + excludeFilters = [ComponentScan.Filter(value = [MessagingConfig::class, MessagingController::class, ProducerConfiguration::class, + MessagingControllerTest.ConsumerConfiguration::class, MessagingControllerTest::class], type = FilterType.ASSIGNABLE_TYPE)]) @TestPropertySource(locations = ["classpath:application-test.properties"]) class BluePrintManagementGRPCHandlerTest { diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintProcessingGRPCHandlerTest.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintProcessingGRPCHandlerTest.kt index f8b972e64..5072b3c6a 100644 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintProcessingGRPCHandlerTest.kt +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/BluePrintProcessingGRPCHandlerTest.kt @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit4.SpringRunner @@ -45,7 +46,8 @@ import kotlin.test.BeforeTest @RunWith(SpringRunner::class) @DirtiesContext @EnableAutoConfiguration -@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"], + excludeFilters =arrayOf(ComponentScan.Filter(value = [(MessagingController::class)], type = FilterType.ASSIGNABLE_TYPE))) @TestPropertySource(locations = ["classpath:application-test.properties"]) class BluePrintProcessingGRPCHandlerTest { private val log = LoggerFactory.getLogger(BluePrintProcessingGRPCHandlerTest::class.java) diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceHandlerTest.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceHandlerTest.kt index 9cbd898dc..65b41262b 100644 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceHandlerTest.kt +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceHandlerTest.kt @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.security.SecurityProperties import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType import org.springframework.core.io.ByteArrayResource import org.springframework.http.client.MultipartBodyBuilder import org.springframework.test.context.ContextConfiguration @@ -49,7 +50,8 @@ import kotlin.test.assertTrue @RunWith(SpringRunner::class) @WebFluxTest @ContextConfiguration(classes = [ExecutionServiceHandler::class, BluePrintCoreConfiguration::class, BluePrintCatalogService::class, SecurityProperties::class]) -@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"], + excludeFilters =arrayOf(ComponentScan.Filter(value = [(MessagingController::class)], type = FilterType.ASSIGNABLE_TYPE))) @TestPropertySource(locations = ["classpath:application-test.properties"]) class ExecutionServiceHandlerTest { diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/MessagingControllerTest.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/MessagingControllerTest.kt new file mode 100644 index 000000000..f7459f522 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/MessagingControllerTest.kt @@ -0,0 +1,211 @@ +/* + * Copyright © 2019 Bell Canada + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.messaginglib + +import com.fasterxml.jackson.databind.node.ObjectNode +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.runBlocking +import org.apache.commons.lang.builder.ToStringBuilder +import org.apache.kafka.clients.CommonClientConfigs +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ActionIdentifiers +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.CommonHeader +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.StepData +import org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.MessagingController +import org.onap.ccsdk.cds.controllerblueprints.core.deleteDir +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.security.SecurityProperties +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.core.io.ByteArrayResource +import org.springframework.http.client.MultipartBodyBuilder +import org.springframework.kafka.annotation.EnableKafka +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.kafka.annotation.PartitionOffset +import org.springframework.kafka.annotation.TopicPartition +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.test.context.EmbeddedKafka +import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit4.SpringRunner +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.returnResult +import org.springframework.web.reactive.function.BodyInserters +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.test.assertNotNull + +@RunWith(SpringRunner::class) +@EnableAutoConfiguration +@ContextConfiguration(classes = [MessagingControllerTest::class, SecurityProperties::class]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +@DirtiesContext +@EmbeddedKafka(ports = [9092]) +@WebFluxTest +class MessagingControllerTest { + + private val log = LoggerFactory.getLogger(MessagingControllerTest::class.java)!! + + @Autowired + lateinit var controller: MessagingController + + @Value("\${blueprintsprocessor.messageclient.self-service-api.consumerTopic}") + lateinit var topicUsedForConsumer: String + + @Autowired + lateinit var kt: KafkaTemplate + + @Autowired + lateinit var webTestClient: WebTestClient + + var receivedEvent: String? = null + + @Before + fun setup() { + deleteDir("target", "blueprints") + uploadBluePrint() + } + + @After + fun clean() { + deleteDir("target", "blueprints") + } + + @Test + fun testReceive() { + val samplePayload = "{\n" + + " \"resource-assignment-request\": {\n" + + " \"artifact-name\": [\"hostname\"],\n" + + " \"store-result\": true,\n" + + " \"resource-assignment-properties\" : {\n" + + " \"hostname\": \"demo123\"\n" + + " }\n" + + " }\n" + + " }" + + kt.defaultTopic = topicUsedForConsumer + + val input = ExecutionServiceInput().apply { + commonHeader = CommonHeader().apply { + originatorId = "1" + requestId = "1234" + subRequestId = "1234-1234" + } + + actionIdentifiers = ActionIdentifiers().apply { + blueprintName = "golden" + blueprintVersion = "1.0.0" + actionName = "resource-assignment" + mode = "sync" + } + + stepData = StepData().apply { + name = "resource-assignment" + } + + payload = JacksonUtils.jsonNode(samplePayload) as ObjectNode + } + + kt.sendDefault(input) + log.info("test-sender sent message='{}'", ToStringBuilder.reflectionToString(input)) + + Thread.sleep(1000) + } + + @KafkaListener(topicPartitions = [TopicPartition(topic = "\${blueprintsprocessor.messageclient.self-service-api.topic}", partitionOffsets = [PartitionOffset(partition = "0", initialOffset = "0")])]) + fun receivedEventFromBluePrintProducer(event: ExecutionServiceInput) { + assertNotNull(event) + } + + private fun uploadBluePrint() { + runBlocking { + val body = MultipartBodyBuilder().apply { + part("file", object : ByteArrayResource(Files.readAllBytes(loadCbaArchive().toPath())) { + override fun getFilename(): String { + return "test-cba.zip" + } + }) + }.build() + + webTestClient + .post() + .uri("/api/v1/execution-service/upload") + .body(BodyInserters.fromMultipartData(body)) + .exchange() + .expectStatus().isOk + .returnResult() + .responseBody + .awaitSingle() + } + } + + private fun loadCbaArchive():File { + return Paths.get("./src/test/resources/cba-for-kafka-integration.zip").toFile() + } + + @Configuration + @EnableKafka + open class ConsumerConfiguration { + + @Value("\${blueprintsprocessor.messageclient.self-service-api.bootstrapServers}") + lateinit var bootstrapServers: String + + @Value("\${blueprintsprocessor.messageclient.self-service-api.groupId}") + lateinit var groupId:String + + @Bean + open fun consumerFactory2(): ConsumerFactory? { + val configProperties = hashMapOf() + configProperties[CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + configProperties[ConsumerConfig.GROUP_ID_CONFIG] = groupId + configProperties[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name + configProperties[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java.name + configProperties[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = "earliest" + configProperties[ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG] = 1000 + + return DefaultKafkaConsumerFactory(configProperties, StringDeserializer(), + JsonDeserializer(ExecutionServiceInput::class.java)) + } + + @Bean + open fun listenerFactory(): ConcurrentKafkaListenerContainerFactory { + val factory = ConcurrentKafkaListenerContainerFactory() + factory.consumerFactory = consumerFactory2() + return factory + } + } +} + + diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/ProducerConfiguration.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/ProducerConfiguration.kt new file mode 100644 index 000000000..dc1f38a63 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/messaginglib/ProducerConfiguration.kt @@ -0,0 +1,48 @@ +/* + * Copyright © 2019 Bell Canada + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.messaginglib + +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringSerializer +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.kafka.annotation.EnableKafka +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.support.serializer.JsonSerializer + +@Configuration +open class ProducerConfiguration { + + @Value("\${blueprintsprocessor.messageclient.self-service-api.bootstrapServers}") + lateinit var bootstrapServers: String + + open fun kpf(): ProducerFactory { + val configs = HashMap() + configs[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + configs[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + configs[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return DefaultKafkaProducerFactory(configs) + } + + @Bean + open fun kt(): KafkaTemplate { + return KafkaTemplate(kpf()) + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/application-test.properties b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/application-test.properties index 6705523df..d532b1582 100644 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/application-test.properties +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/application-test.properties @@ -31,3 +31,12 @@ blueprintsprocessor.blueprintArchivePath=./target/blueprints/archive # Python executor blueprints.processor.functions.python.executor.executionPath=./../../../../components/scripts/python/ccsdk_blueprints blueprints.processor.functions.python.executor.modulePaths=./../../../../components/scripts/python/ccsdk_blueprints + +# Kafka-message-lib Configuration +blueprintsprocessor.messageclient.self-service-api.kafkaEnable=true +blueprintsprocessor.messageclient.self-service-api.topic=producer.t +blueprintsprocessor.messageclient.self-service-api.type=kafka-basic-auth +blueprintsprocessor.messageclient.self-service-api.bootstrapServers=127.0.0.1:9092 +blueprintsprocessor.messageclient.self-service-api.consumerTopic=receiver.t +blueprintsprocessor.messageclient.self-service-api.groupId=receiver-id +blueprintsprocessor.messageclient.self-service-api.clientId=default-client-id diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/cba-for-kafka-integration.zip b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/test/resources/cba-for-kafka-integration.zip new file mode 100644 index 0000000000000000000000000000000000000000..23070380c6b2264394060a316451d3a7dc9d0b0b GIT binary patch literal 20700 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 + + + ${project.groupId} + message-lib + ${project.version} + + -- 2.16.6