diff --git a/.github/workflows/bekreftelse-api.yaml b/.github/workflows/bekreftelse-api.yaml new file mode 100644 index 00000000..e681b43b --- /dev/null +++ b/.github/workflows/bekreftelse-api.yaml @@ -0,0 +1,102 @@ +name: Bekreftelse API + +on: + push: + branches: + - main + - dev/* + paths: + - 'apps/bekreftelse-api/**' + - 'lib/**' + - 'domain/**' + - '.github/workflows/bekreftelse-api.yaml' + - 'gradle/**' + - 'settings.gradle.kts' + - 'gradle.properties' + - 'gradlew' + - 'gradlew.bat' + +env: + MODULE: bekreftelse-api + IMAGE: europe-north1-docker.pkg.dev/${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}/paw/paw-arbeidssoeker-bekreftelse-api +jobs: + build: + name: Build + permissions: + contents: read + id-token: write + packages: write + runs-on: ubuntu-latest + outputs: + image: ${{ steps.docker-build-push.outputs.image }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: temurin + cache: gradle + - name: Set version + run: echo "VERSION=$(date +'%y.%m.%d').${{ github.run_number }}-${{ github.run_attempt }}" >> $GITHUB_ENV + - name: Login GAR + uses: nais/login@v0 + with: + project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }} + identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }} + team: paw + - name: Build and push image with Gradle + id: docker-build-push + working-directory: ./ + env: + ORG_GRADLE_PROJECT_githubPassword: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "image=${{ env.IMAGE }}:${{ env.VERSION }}" >> $GITHUB_OUTPUT + echo -Pversion=${{ env.VERSION }} -Pimage=${{ env.IMAGE }} :apps:${{ env.MODULE }}:build :apps:${{ env.MODULE }}:jib + ./gradlew -Pversion=${{ env.VERSION }} -Pimage=${{ env.IMAGE }} :apps:${{ env.MODULE }}:build :apps:${{ env.MODULE }}:jib + echo "DIGEST=$(cat ./apps/${{ env.MODULE }}/build/jib-image.digest)" >> $GITHUB_ENV + - name: Attest and sign image + uses: nais/attest-sign@v1.3.4 + with: + image_ref: ${{ env.IMAGE }}@${{ env.DIGEST }} + + deploy-dev: + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/dev') + name: Deploy to dev-gcp + needs: + - build + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Deploy to GCP + uses: nais/deploy/actions/deploy@v2 + env: + CLUSTER: dev-gcp + RESOURCE: ./apps/${{ env.MODULE }}/nais/nais-dev.yaml + VAR: image=${{ needs.build.outputs.image }},kafka=nav-dev + +# deploy-prod: +# if: github.ref == 'refs/heads/main' +# name: Deploy to prod-gcp +# needs: +# - build +# - deploy-dev +# permissions: +# contents: read +# id-token: write +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# - name: Deploy to GCP +# uses: nais/deploy/actions/deploy@v2 +# env: +# TEAM: paw +# CLUSTER: prod-gcp +# RESOURCE: ./apps/${{ env.MODULE }}/nais/nais-prod.yaml +# VAR: image=${{ needs.build.outputs.image }},kafka=nav-prod diff --git a/apps/bekreftelse-api/build.gradle.kts b/apps/bekreftelse-api/build.gradle.kts index 39811230..1ed88fdd 100644 --- a/apps/bekreftelse-api/build.gradle.kts +++ b/apps/bekreftelse-api/build.gradle.kts @@ -14,10 +14,11 @@ val image: String? by project dependencies { // Project implementation(project(":lib:hoplite-config")) + implementation(project(":lib:error-handling")) implementation(project(":lib:kafka-streams")) implementation(project(":lib:kafka-key-generator-client")) implementation(project(":domain:bekreftelse-interne-hendelser")) - implementation(project(":domain:bekreftelsesmelding-schema")) + implementation(project(":domain:bekreftelsesmelding-avro-schema")) // Server implementation(ktorServer.bundles.withNettyAndMicrometer) @@ -73,7 +74,7 @@ java { } application { - mainClass.set("no.nav.paw.rapportering.api.ApplicationKt") + mainClass.set("no.nav.paw.bekreftelse.api.ApplicationKt") } tasks.withType().configureEach { diff --git a/apps/bekreftelse-api/nais/nais-dev.yaml b/apps/bekreftelse-api/nais/nais-dev.yaml index 5d7afb19..9912b1ad 100644 --- a/apps/bekreftelse-api/nais/nais-dev.yaml +++ b/apps/bekreftelse-api/nais/nais-dev.yaml @@ -1,16 +1,16 @@ apiVersion: nais.io/v1alpha1 kind: Application metadata: - name: paw-rapportering-api + name: paw-arbeidssoeker-bekreftelse-api namespace: paw labels: team: paw spec: image: {{ image }} port: 8080 - env: - - name: KAFKA_KEY_SCOPE - value: "api://dev-gcp.paw.paw-kafka-key-generator/.default" + replicas: + min: 1 + max: 1 resources: limits: memory: 1024Mi @@ -26,9 +26,6 @@ spec: claims: extra: - NAVident - replicas: - min: 1 - max: 1 liveness: path: /internal/isAlive initialDelay: 10 @@ -43,4 +40,18 @@ spec: enabled: true runtime: java kafka: - pool: nav-dev + pool: {{ kafka }} + streams: true + accessPolicy: + inbound: + rules: + - application: tokenx-token-generator + namespace: aura + - application: azure-token-generator + namespace: aura + outbound: + rules: + - application: paw-kafka-key-generator + namespace: paw + - application: poao-tilgang + namespace: poao diff --git a/apps/bekreftelse-api/nais/nais-prod.yaml b/apps/bekreftelse-api/nais/nais-prod.yaml index a17d0c11..9912b1ad 100644 --- a/apps/bekreftelse-api/nais/nais-prod.yaml +++ b/apps/bekreftelse-api/nais/nais-prod.yaml @@ -1,16 +1,16 @@ apiVersion: nais.io/v1alpha1 kind: Application metadata: - name: paw-rapportering-api + name: paw-arbeidssoeker-bekreftelse-api namespace: paw labels: team: paw spec: image: {{ image }} port: 8080 - env: - - name: KAFKA_KEY_SCOPE - value: "api://prod-gcp.paw.paw-kafka-key-generator/.default" + replicas: + min: 1 + max: 1 resources: limits: memory: 1024Mi @@ -26,9 +26,6 @@ spec: claims: extra: - NAVident - replicas: - min: 1 - max: 1 liveness: path: /internal/isAlive initialDelay: 10 @@ -43,4 +40,18 @@ spec: enabled: true runtime: java kafka: - pool: nav-prod + pool: {{ kafka }} + streams: true + accessPolicy: + inbound: + rules: + - application: tokenx-token-generator + namespace: aura + - application: azure-token-generator + namespace: aura + outbound: + rules: + - application: paw-kafka-key-generator + namespace: paw + - application: poao-tilgang + namespace: poao diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Application.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Application.kt index cb024c0d..de7bd925 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Application.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Application.kt @@ -5,48 +5,41 @@ import io.ktor.server.engine.addShutdownHook import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.server.routing.routing -import no.nav.paw.config.hoplite.loadNaisOrLocalConfiguration -import no.nav.paw.config.kafka.KAFKA_CONFIG_WITH_SCHEME_REG -import no.nav.paw.config.kafka.KAFKA_STREAMS_CONFIG_WITH_SCHEME_REG -import no.nav.paw.config.kafka.KafkaConfig -import no.nav.paw.kafkakeygenerator.auth.AzureM2MConfig -import no.nav.paw.kafkakeygenerator.client.KafkaKeyConfig import no.nav.paw.bekreftelse.api.config.APPLICATION_CONFIG_FILE_NAME import no.nav.paw.bekreftelse.api.config.ApplicationConfig +import no.nav.paw.bekreftelse.api.config.SERVER_CONFIG_FILE_NAME +import no.nav.paw.bekreftelse.api.config.ServerConfig import no.nav.paw.bekreftelse.api.plugins.configureAuthentication import no.nav.paw.bekreftelse.api.plugins.configureHTTP +import no.nav.paw.bekreftelse.api.plugins.configureKafka import no.nav.paw.bekreftelse.api.plugins.configureLogging import no.nav.paw.bekreftelse.api.plugins.configureMetrics -import no.nav.paw.bekreftelse.api.plugins.configureOtel import no.nav.paw.bekreftelse.api.plugins.configureSerialization -import no.nav.paw.bekreftelse.api.routes.healthRoutes +import no.nav.paw.bekreftelse.api.plugins.configureTracing import no.nav.paw.bekreftelse.api.routes.bekreftelseRoutes +import no.nav.paw.bekreftelse.api.routes.metricsRoutes import no.nav.paw.bekreftelse.api.routes.swaggerRoutes +import no.nav.paw.config.hoplite.loadNaisOrLocalConfiguration +import no.nav.paw.health.route.healthRoutes import org.slf4j.LoggerFactory fun main() { - val logger = LoggerFactory.getLogger("rapportering-api") - logger.info("Starter: ${ApplicationInfo.id}") + val logger = LoggerFactory.getLogger("no.nav.paw.logger.application") val applicationConfig = loadNaisOrLocalConfiguration(APPLICATION_CONFIG_FILE_NAME) - val kafkaConfig = loadNaisOrLocalConfiguration(KAFKA_CONFIG_WITH_SCHEME_REG) - val kafkaStreamsConfig = loadNaisOrLocalConfiguration(KAFKA_STREAMS_CONFIG_WITH_SCHEME_REG) - val azureM2MConfig = loadNaisOrLocalConfiguration("azure_m2m_key_config.toml") - val kafkaKeyConfig = loadNaisOrLocalConfiguration("kafka_key_generator_client_config.toml") + val serverConfig = loadNaisOrLocalConfiguration(SERVER_CONFIG_FILE_NAME) - val dependencies = createDependencies( - applicationConfig, - kafkaConfig, - kafkaStreamsConfig, - azureM2MConfig, - kafkaKeyConfig - ) + logger.info("Starter: ${applicationConfig.appId}") - embeddedServer(Netty, port = 8080) { - module(applicationConfig, dependencies) - }.apply { - addShutdownHook { stop(300, 300) } - start(wait = true) + val dependencies = createDependencies(applicationConfig) + + with(serverConfig) { + embeddedServer(Netty, port = port) { + module(applicationConfig, dependencies) + }.apply { + addShutdownHook { stop(gracePeriodMillis, timeoutMillis) } + start(wait = true) + } } } @@ -56,22 +49,22 @@ fun Application.module( ) { configureMetrics(dependencies.prometheusMeterRegistry) configureHTTP() - configureAuthentication(applicationConfig.authProviders) + configureAuthentication(applicationConfig) configureLogging() configureSerialization() - configureOtel() + configureTracing() + if (!applicationConfig.brukMock) { // TODO Bruker mock for utvikling + configureKafka(applicationConfig, listOf(dependencies.bekreftelseKafkaStreams)) + } routing { - healthRoutes(dependencies.prometheusMeterRegistry, dependencies.health) + healthRoutes(dependencies.healthIndicatorRepository) + metricsRoutes(dependencies.prometheusMeterRegistry) swaggerRoutes() bekreftelseRoutes( - kafkaKeyClient = dependencies.kafkaKeysClient, - bekreftelseStateStore = dependencies.bekreftelseStateStore, - stateStoreName = applicationConfig.bekreftelseStateStoreName, - kafkaStreams = dependencies.kafkaStreams, - httpClient = dependencies.httpClient, - bekreftelseProducer = dependencies.bekreftelseProducer, - autorisasjonService = dependencies.autorisasjonService + dependencies.kafkaKeysClient, + dependencies.bekreftelseService, + dependencies.autorisasjonService ) } } diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/ApplicationInfo.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/ApplicationInfo.kt deleted file mode 100644 index a9bb7fb3..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/ApplicationInfo.kt +++ /dev/null @@ -1,5 +0,0 @@ -package no.nav.paw.bekreftelse.api - -object ApplicationInfo { - val id = System.getenv("IMAGE_WITH_VERSION") ?: "UNSPECIFIED" -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Dependencies.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Dependencies.kt index 87e743fa..c4ab0779 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Dependencies.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Dependencies.kt @@ -1,104 +1,43 @@ package no.nav.paw.bekreftelse.api -import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.serialization.jackson.jackson import io.micrometer.prometheusmetrics.PrometheusConfig import io.micrometer.prometheusmetrics.PrometheusMeterRegistry -import no.nav.paw.config.kafka.KafkaConfig -import no.nav.paw.config.kafka.streams.KafkaStreamsFactory -import no.nav.paw.kafkakeygenerator.auth.AzureM2MConfig -import no.nav.paw.kafkakeygenerator.auth.azureAdM2MTokenClient -import no.nav.paw.kafkakeygenerator.client.KafkaKeyConfig -import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient -import no.nav.paw.kafkakeygenerator.client.kafkaKeysClient import no.nav.paw.bekreftelse.api.config.ApplicationConfig import no.nav.paw.bekreftelse.api.kafka.BekreftelseProducer -import no.nav.paw.bekreftelse.api.kafka.InternState -import no.nav.paw.bekreftelse.api.kafka.InternStateSerde -import no.nav.paw.bekreftelse.api.kafka.appTopology +import no.nav.paw.bekreftelse.api.kafka.buildBekreftelseTopology +import no.nav.paw.bekreftelse.api.plugins.buildKafkaStreams import no.nav.paw.bekreftelse.api.services.AutorisasjonService +import no.nav.paw.bekreftelse.api.services.BekreftelseService +import no.nav.paw.bekreftelse.api.services.BekreftelseServiceImpl +import no.nav.paw.bekreftelse.api.services.BekreftelseServiceMock +import no.nav.paw.health.repository.HealthIndicatorRepository +import no.nav.paw.kafkakeygenerator.auth.azureAdM2MTokenClient +import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient +import no.nav.paw.kafkakeygenerator.client.kafkaKeysClient import no.nav.poao_tilgang.client.PoaoTilgangCachedClient import no.nav.poao_tilgang.client.PoaoTilgangHttpClient -import org.apache.kafka.common.serialization.Serdes import org.apache.kafka.streams.KafkaStreams -import org.apache.kafka.streams.StoreQueryParameters -import org.apache.kafka.streams.StreamsBuilder -import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler -import org.apache.kafka.streams.state.QueryableStoreTypes -import org.apache.kafka.streams.state.ReadOnlyKeyValueStore -import org.apache.kafka.streams.state.Stores -import org.slf4j.LoggerFactory -fun createDependencies( - applicationConfig: ApplicationConfig, - kafkaConfig: KafkaConfig, - kafkaStreamsConfig: KafkaConfig, - azureM2MConfig: AzureM2MConfig, - kafkaKeyConfig: KafkaKeyConfig -): Dependencies { - val logger = LoggerFactory.getLogger("rapportering-api") +fun createDependencies(applicationConfig: ApplicationConfig): Dependencies { + val azureM2MTokenClient = azureAdM2MTokenClient(applicationConfig.naisEnv, applicationConfig.azureM2M) - val azureM2MTokenClient = azureAdM2MTokenClient(applicationConfig.naisEnv, azureM2MConfig) - - val kafkaKeysClient = kafkaKeysClient(kafkaKeyConfig) { - azureM2MTokenClient.createMachineToMachineToken(kafkaKeyConfig.scope) + val kafkaKeysClient = kafkaKeysClient(applicationConfig.kafkaKeysClient) { + azureM2MTokenClient.createMachineToMachineToken(applicationConfig.kafkaKeysClient.scope) } val prometheusMeterRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) + val healthIndicatorRepository = HealthIndicatorRepository() + val httpClient = HttpClient { install(ContentNegotiation) { jackson() } } - val streamsConfig = KafkaStreamsFactory(applicationConfig.applicationIdSuffix, kafkaStreamsConfig) - .withDefaultKeySerde(Serdes.LongSerde::class) - .withDefaultValueSerde(SpecificAvroSerde::class) - - val streamsBuilder = StreamsBuilder() - .addStateStore( - Stores.keyValueStoreBuilder( - Stores.persistentKeyValueStore(applicationConfig.bekreftelseStateStoreName), - Serdes.Long(), - InternStateSerde(), - ) - ) - - val topology = streamsBuilder.appTopology( - prometheusRegistry = prometheusMeterRegistry, - bekreftelseHendelseLoggTopic = applicationConfig.bekreftelseHendelseLoggTopic, - bekreftelseStateStoreName = applicationConfig.bekreftelseStateStoreName, - ) - - val kafkaStreams = KafkaStreams( - topology, - streamsConfig.properties.apply { - put("application.server", applicationConfig.hostname) - } - ) - - kafkaStreams.setUncaughtExceptionHandler { throwable -> - logger.error("Uventet feil: ${throwable.message}", throwable) - StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.SHUTDOWN_APPLICATION - } - - kafkaStreams.start() - - val bekreftelseStateStore: ReadOnlyKeyValueStore = kafkaStreams.store( - StoreQueryParameters.fromNameAndType( - applicationConfig.bekreftelseStateStoreName, - QueryableStoreTypes.keyValueStore() - ) - ) - - val health = Health(kafkaStreams) - - val bekreftelseProducer = BekreftelseProducer(kafkaConfig, applicationConfig) - - val poaoTilgangClient = PoaoTilgangCachedClient( PoaoTilgangHttpClient( applicationConfig.poaoClientConfig.url, @@ -108,25 +47,41 @@ fun createDependencies( val autorisasjonService = AutorisasjonService(poaoTilgangClient) + val bekreftelseTopology = buildBekreftelseTopology(applicationConfig, prometheusMeterRegistry) + val bekreftelseKafkaStreams = buildKafkaStreams(applicationConfig, healthIndicatorRepository, bekreftelseTopology) + + // TODO Bruker mock for utvikling + val bekreftelseService: BekreftelseService = if (applicationConfig.brukMock) { + healthIndicatorRepository.getLivenessIndicators().forEach { it.setHealthy() } + healthIndicatorRepository.getReadinessIndicators().forEach { it.setHealthy() } + + BekreftelseServiceMock() + } else { + val bekreftelseProducer = BekreftelseProducer(applicationConfig) + + BekreftelseServiceImpl( + applicationConfig, + httpClient, + bekreftelseKafkaStreams, + bekreftelseProducer + ) + } + return Dependencies( kafkaKeysClient, - httpClient, - kafkaStreams, prometheusMeterRegistry, - bekreftelseStateStore, - health, - bekreftelseProducer, - autorisasjonService + healthIndicatorRepository, + bekreftelseKafkaStreams, + autorisasjonService, + bekreftelseService ) } data class Dependencies( val kafkaKeysClient: KafkaKeysClient, - val httpClient: HttpClient, - val kafkaStreams: KafkaStreams, val prometheusMeterRegistry: PrometheusMeterRegistry, - val bekreftelseStateStore: ReadOnlyKeyValueStore, - val health: Health, - val bekreftelseProducer: BekreftelseProducer, - val autorisasjonService: AutorisasjonService + val healthIndicatorRepository: HealthIndicatorRepository, + val bekreftelseKafkaStreams: KafkaStreams, + val autorisasjonService: AutorisasjonService, + val bekreftelseService: BekreftelseService ) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Health.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Health.kt deleted file mode 100644 index 38ed2b23..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/Health.kt +++ /dev/null @@ -1,44 +0,0 @@ -package no.nav.paw.bekreftelse.api - -import io.ktor.http.HttpStatusCode -import org.apache.kafka.streams.KafkaStreams - -class Health(private val kafkaStreams: KafkaStreams) { - fun alive(): Status { - val state = kafkaStreams.state() - val httpStatusCode = when (state) { - KafkaStreams.State.CREATED -> HttpStatusCode.OK - KafkaStreams.State.REBALANCING -> HttpStatusCode.OK - KafkaStreams.State.RUNNING -> HttpStatusCode.OK - KafkaStreams.State.PENDING_SHUTDOWN -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.NOT_RUNNING -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.PENDING_ERROR -> HttpStatusCode.InternalServerError - KafkaStreams.State.ERROR -> HttpStatusCode.InternalServerError - null -> HttpStatusCode.InternalServerError - } - return status(httpStatusCode, state) - } - - fun ready(): Status { - val state = kafkaStreams.state() - val httpStatusCode = when (state) { - KafkaStreams.State.RUNNING -> HttpStatusCode.OK - KafkaStreams.State.CREATED -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.REBALANCING -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.PENDING_SHUTDOWN -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.NOT_RUNNING -> HttpStatusCode.ServiceUnavailable - KafkaStreams.State.PENDING_ERROR -> HttpStatusCode.InternalServerError - KafkaStreams.State.ERROR -> HttpStatusCode.InternalServerError - null -> HttpStatusCode.InternalServerError - } - return status(httpStatusCode, state) - } - - private fun status(kode: HttpStatusCode, kafkaStreamsTilstand: KafkaStreams.State?): Status = - Status(kode, "KafkaStreams tilstand: '${kafkaStreamsTilstand?.name}'") -} - -data class Status( - val code: HttpStatusCode, - val message: String -) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ApplicationConfig.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ApplicationConfig.kt index 21c4ac88..72888a5d 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ApplicationConfig.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ApplicationConfig.kt @@ -1,21 +1,38 @@ package no.nav.paw.bekreftelse.api.config import no.nav.paw.config.env.NaisEnv +import no.nav.paw.config.env.currentAppId +import no.nav.paw.config.env.currentAppName import no.nav.paw.config.env.currentNaisEnv +import no.nav.paw.config.kafka.KafkaConfig +import no.nav.paw.kafkakeygenerator.auth.AzureM2MConfig +import no.nav.paw.kafkakeygenerator.client.KafkaKeyConfig import java.net.InetAddress const val APPLICATION_CONFIG_FILE_NAME = "application_config.toml" data class ApplicationConfig( - val applicationIdSuffix: String, - val producerId: String, - val bekreftelseTopic: String, - val bekreftelseHendelseLoggTopic: String, - val bekreftelseStateStoreName: String, + val kafkaTopology: KafkaTopologyConfig, val authProviders: AuthProviders, + val azureM2M: AzureM2MConfig, + val poaoClientConfig: ServiceClientConfig, + val kafkaKeysClient: KafkaKeyConfig, + val kafkaClients: KafkaConfig, + // Env val naisEnv: NaisEnv = currentNaisEnv, + val appId: String = currentAppId ?: "UNSPECIFIED", + val appName: String = currentAppName ?: "UNSPECIFIED", val hostname: String = InetAddress.getLocalHost().hostName, - val poaoClientConfig: ServiceClientConfig + + val brukMock: Boolean = true +) + +data class KafkaTopologyConfig( + val applicationIdSuffix: String, + val producerId: String, + val bekreftelseTopic: String, + val bekreftelseHendelsesloggTopic: String, + val internStateStoreName: String ) data class ServiceClientConfig( diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ServerConfig.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ServerConfig.kt new file mode 100644 index 00000000..0ddda9b8 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/config/ServerConfig.kt @@ -0,0 +1,12 @@ +package no.nav.paw.bekreftelse.api.config + +const val SERVER_CONFIG_FILE_NAME = "server_config.toml" + +data class ServerConfig( + val port: Int, + val callGroupSize: Int, + val workerGroupSize: Int, + val connectionGroupSize: Int, + val gracePeriodMillis: Long, + val timeoutMillis: Long +) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseHendelseProcessor.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseHendelseProcessor.kt index 289921e6..190652bf 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseHendelseProcessor.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseHendelseProcessor.kt @@ -1,5 +1,6 @@ package no.nav.paw.bekreftelse.api.kafka +import no.nav.paw.bekreftelse.api.model.InternState import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelse import no.nav.paw.bekreftelse.internehendelser.BekreftelseMeldingMottatt import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseProducer.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseProducer.kt index 3f9c71d5..bfda7cbf 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseProducer.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/BekreftelseProducer.kt @@ -1,43 +1,39 @@ package no.nav.paw.bekreftelse.api.kafka -import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde import no.nav.paw.bekreftelse.api.config.ApplicationConfig -import no.nav.paw.bekreftelse.api.domain.BekreftelseRequest +import no.nav.paw.bekreftelse.api.model.BekreftelseRequest +import no.nav.paw.bekreftelse.api.utils.buildBekreftelseSerde import no.nav.paw.bekreftelse.api.utils.logger import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig -import no.nav.paw.config.kafka.KafkaConfig +import no.nav.paw.bekreftelse.melding.v1.Bekreftelse import no.nav.paw.config.kafka.KafkaFactory import no.nav.paw.config.kafka.sendDeferred -import no.nav.paw.rapportering.melding.v1.Melding import org.apache.kafka.clients.producer.Producer import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.common.serialization.LongSerializer class BekreftelseProducer( - private val kafkaConfig: KafkaConfig, private val applicationConfig: ApplicationConfig, ) { - private lateinit var producer: Producer - private val meldingSerde = SpecificAvroSerde().apply { - configure(mapOf("schema.registry.url" to kafkaConfig.schemaRegistry), false) - } + private lateinit var producer: Producer + private val bekreftelseSerde = buildBekreftelseSerde() init { initializeProducer() } private fun initializeProducer() { - val kafkaFactory = KafkaFactory(kafkaConfig) + val kafkaFactory = KafkaFactory(applicationConfig.kafkaClients) producer = - kafkaFactory.createProducer( - clientId = applicationConfig.producerId, + kafkaFactory.createProducer( + clientId = applicationConfig.kafkaTopology.producerId, keySerializer = LongSerializer::class, - valueSerializer = meldingSerde.serializer()::class + valueSerializer = bekreftelseSerde.serializer()::class ) } - suspend fun produceMessage(key: Long, message: Melding) { - val topic = applicationConfig.bekreftelseTopic + suspend fun produceMessage(key: Long, message: Bekreftelse) { + val topic = applicationConfig.kafkaTopology.bekreftelseTopic val record = ProducerRecord(topic, key, message) val recordMetadata = producer.sendDeferred(record).await() logger.trace("Sendte melding til kafka: offset={}", recordMetadata.offset()) @@ -48,7 +44,7 @@ class BekreftelseProducer( } } -fun createMelding(state: BekreftelseTilgjengelig, bekreftelse: BekreftelseRequest): Melding = TODO() +fun createMelding(state: BekreftelseTilgjengelig, bekreftelse: BekreftelseRequest): Bekreftelse = TODO() //Melding.newBuilder() // .setId(ApplicationInfo.id) // .setNamespace("paw") diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/InternStateSerde.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/InternStateSerde.kt deleted file mode 100644 index 4a1476b4..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/InternStateSerde.kt +++ /dev/null @@ -1,29 +0,0 @@ -package no.nav.paw.bekreftelse.api.kafka - -import no.nav.paw.bekreftelse.api.utils.JsonUtil -import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig -import org.apache.kafka.common.serialization.Deserializer -import org.apache.kafka.common.serialization.Serde -import org.apache.kafka.common.serialization.Serializer - -data class InternState( - val tilgjendeligeBekreftelser: List -) - -class InternStateSerde : Serde { - override fun serializer() = InternStateSerializer() - override fun deserializer(): Deserializer = - InternStateDeserializer() -} - -class InternStateSerializer : Serializer { - override fun serialize(topic: String?, data: InternState?): ByteArray { - return JsonUtil.objectMapper.writeValueAsBytes(data) - } -} - -class InternStateDeserializer : Deserializer { - override fun deserialize(topic: String?, data: ByteArray?): InternState? { - return JsonUtil.objectMapper.readValue(data, InternState::class.java) - } -} diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/Topology.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/Topology.kt index 3c03eb2a..9c486807 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/Topology.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/kafka/Topology.kt @@ -1,19 +1,37 @@ package no.nav.paw.bekreftelse.api.kafka -import io.micrometer.prometheusmetrics.PrometheusMeterRegistry +import io.micrometer.core.instrument.MeterRegistry +import no.nav.paw.bekreftelse.api.config.ApplicationConfig +import no.nav.paw.bekreftelse.api.utils.buildInternStateSerde import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelseSerde import org.apache.kafka.common.serialization.Serdes import org.apache.kafka.streams.StreamsBuilder import org.apache.kafka.streams.Topology import org.apache.kafka.streams.kstream.Consumed +import org.apache.kafka.streams.state.Stores -fun StreamsBuilder.appTopology( - prometheusRegistry: PrometheusMeterRegistry, - bekreftelseHendelseLoggTopic: String, - bekreftelseStateStoreName: String, -): Topology { - stream(bekreftelseHendelseLoggTopic, Consumed.with(Serdes.Long(), BekreftelseHendelseSerde())) - .oppdaterBekreftelseHendelseState(bekreftelseStateStoreName) +fun buildBekreftelseTopology( + applicationConfig: ApplicationConfig, + meterRegistry: MeterRegistry +): Topology = StreamsBuilder().apply { + addInternStateStore(applicationConfig) + addBekreftelseKStream(applicationConfig) +}.build() - return build() +private fun StreamsBuilder.addInternStateStore(applicationConfig: ApplicationConfig) { + addStateStore( + Stores.keyValueStoreBuilder( + Stores.persistentKeyValueStore(applicationConfig.kafkaTopology.internStateStoreName), + Serdes.Long(), + buildInternStateSerde(), + ) + ) +} + +private fun StreamsBuilder.addBekreftelseKStream(applicationConfig: ApplicationConfig) { + stream( + applicationConfig.kafkaTopology.bekreftelseHendelsesloggTopic, + Consumed.with(Serdes.Long(), BekreftelseHendelseSerde()) + ) + .oppdaterBekreftelseHendelseState(applicationConfig.kafkaTopology.internStateStoreName) } \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/BekreftelseRequest.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/BekreftelseRequest.kt similarity index 87% rename from apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/BekreftelseRequest.kt rename to apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/BekreftelseRequest.kt index 27cbd080..a3a53529 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/BekreftelseRequest.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/BekreftelseRequest.kt @@ -1,4 +1,4 @@ -package no.nav.paw.bekreftelse.api.domain +package no.nav.paw.bekreftelse.api.model import java.util.* diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/InternState.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/InternState.kt new file mode 100644 index 00000000..f4dbad78 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/InternState.kt @@ -0,0 +1,7 @@ +package no.nav.paw.bekreftelse.api.model + +import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig + +data class InternState( + val tilgjendeligeBekreftelser: List +) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserRequest.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserRequest.kt similarity index 65% rename from apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserRequest.kt rename to apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserRequest.kt index 1c4f401c..d06706f3 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserRequest.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserRequest.kt @@ -1,4 +1,4 @@ -package no.nav.paw.bekreftelse.api.domain +package no.nav.paw.bekreftelse.api.model data class TilgjengeligeBekreftelserRequest( val identitetsnummer: String? diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserResponse.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserResponse.kt similarity index 93% rename from apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserResponse.kt rename to apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserResponse.kt index 90620ca7..b99b7599 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/domain/TilgjengeligeBekreftelserResponse.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligeBekreftelserResponse.kt @@ -1,4 +1,4 @@ -package no.nav.paw.bekreftelse.api.domain +package no.nav.paw.bekreftelse.api.model import java.time.Instant import java.util.* diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Authentication.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Authentication.kt index a9cd50e0..39fadc84 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Authentication.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Authentication.kt @@ -2,29 +2,32 @@ package no.nav.paw.bekreftelse.api.plugins import io.ktor.server.application.Application import io.ktor.server.auth.authentication -import no.nav.paw.bekreftelse.api.config.AuthProviders +import no.nav.paw.bekreftelse.api.config.ApplicationConfig import no.nav.security.token.support.v2.IssuerConfig import no.nav.security.token.support.v2.RequiredClaims import no.nav.security.token.support.v2.TokenSupportConfig import no.nav.security.token.support.v2.tokenValidationSupport -fun Application.configureAuthentication(authProviders: AuthProviders) = - authentication { - authProviders.forEach { provider -> - tokenValidationSupport( - name = provider.name, - requiredClaims = RequiredClaims( - provider.name, - provider.claims.map.toTypedArray(), - provider.claims.combineWithOr - ), - config = TokenSupportConfig( - IssuerConfig( - name = provider.name, - discoveryUrl = provider.discoveryUrl, - acceptedAudience = listOf(provider.clientId) +fun Application.configureAuthentication(applicationConfig: ApplicationConfig) { + with(applicationConfig) { + authentication { + authProviders.forEach { provider -> + tokenValidationSupport( + name = provider.name, + requiredClaims = RequiredClaims( + provider.name, + provider.claims.map.toTypedArray(), + provider.claims.combineWithOr + ), + config = TokenSupportConfig( + IssuerConfig( + name = provider.name, + discoveryUrl = provider.discoveryUrl, + acceptedAudience = listOf(provider.clientId) + ) ) ) - ) + } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Kafka.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Kafka.kt new file mode 100644 index 00000000..ad8f0857 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Kafka.kt @@ -0,0 +1,18 @@ +package no.nav.paw.bekreftelse.api.plugins + + +import io.ktor.server.application.Application +import io.ktor.server.application.install +import no.nav.paw.bekreftelse.api.config.ApplicationConfig +import org.apache.kafka.streams.KafkaStreams +import java.time.Duration + +fun Application.configureKafka( + applicationConfig: ApplicationConfig, + kafkaStreamsList: List +) { + install(KafkaStreamsPlugin) { + shutDownTimeout = Duration.ofSeconds(5) // TODO Legg i konfig + kafkaStreams = kafkaStreamsList + } +} diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/KafkaStreams.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/KafkaStreams.kt new file mode 100644 index 00000000..28dbd810 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/KafkaStreams.kt @@ -0,0 +1,69 @@ +package no.nav.paw.bekreftelse.api.plugins + +import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde +import io.ktor.server.application.ApplicationPlugin +import io.ktor.server.application.ApplicationStarted +import io.ktor.server.application.ApplicationStopping +import io.ktor.server.application.createApplicationPlugin +import io.ktor.server.application.hooks.MonitoringEvent +import io.ktor.server.application.log +import io.ktor.util.KtorDsl +import no.nav.paw.bekreftelse.api.config.ApplicationConfig +import no.nav.paw.config.kafka.streams.KafkaStreamsFactory +import no.nav.paw.error.handler.withApplicationTerminatingExceptionHandler +import no.nav.paw.health.listener.withHealthIndicatorStateListener +import no.nav.paw.health.model.LivenessHealthIndicator +import no.nav.paw.health.model.ReadinessHealthIndicator +import no.nav.paw.health.repository.HealthIndicatorRepository +import org.apache.kafka.common.serialization.Serdes +import org.apache.kafka.streams.KafkaStreams +import org.apache.kafka.streams.StreamsConfig +import org.apache.kafka.streams.Topology +import java.time.Duration + +@KtorDsl +class KafkaStreamsPluginConfig { + var shutDownTimeout: Duration? = null + var kafkaStreams: List? = null +} + +val KafkaStreamsPlugin: ApplicationPlugin = + createApplicationPlugin("KafkaStreams", ::KafkaStreamsPluginConfig) { + val shutDownTimeout = requireNotNull(pluginConfig.shutDownTimeout) { "ShutDownTimeout er null" } + val kafkaStreams = requireNotNull(pluginConfig.kafkaStreams) { "KafkaStreams er null" } + + on(MonitoringEvent(ApplicationStarted)) { application -> + application.log.info("Starter Kafka Streams") + kafkaStreams.forEach { stream -> stream.start() } + } + + on(MonitoringEvent(ApplicationStopping)) { application -> + application.log.info("Stopper Kafka Streams") + kafkaStreams.forEach { stream -> stream.close(shutDownTimeout) } + } + } + +fun buildKafkaStreams( + applicationConfig: ApplicationConfig, + healthIndicatorRepository: HealthIndicatorRepository, + topology: Topology +): KafkaStreams { + val livenessIndicator = healthIndicatorRepository.addLivenessIndicator(LivenessHealthIndicator()) + val readinessIndicator = healthIndicatorRepository.addReadinessIndicator(ReadinessHealthIndicator()) + + val streamsFactory = KafkaStreamsFactory( + applicationConfig.kafkaTopology.applicationIdSuffix, + applicationConfig.kafkaClients + ) + .withDefaultKeySerde(Serdes.Long()::class) + .withDefaultValueSerde(SpecificAvroSerde::class) + .apply { properties["application.server"] = applicationConfig.hostname } + + val kafkaStreams = KafkaStreams( + topology, + StreamsConfig(streamsFactory.properties) + ) + kafkaStreams.withHealthIndicatorStateListener(livenessIndicator, readinessIndicator) + kafkaStreams.withApplicationTerminatingExceptionHandler() + return kafkaStreams +} diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Otel.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Tracing.kt similarity index 93% rename from apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Otel.kt rename to apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Tracing.kt index 50a40032..9b6a70e5 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Otel.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/plugins/Tracing.kt @@ -6,7 +6,7 @@ import io.ktor.server.application.install import io.opentelemetry.api.trace.Span -fun Application.configureOtel() { +fun Application.configureTracing() { install( createApplicationPlugin("OtelTraceIdPlugin") { onCallRespond { call, _ -> diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt index 922de63f..5f23456c 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt @@ -1,10 +1,5 @@ package no.nav.paw.bekreftelse.api.routes -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.bearerAuth -import io.ktor.client.request.post -import io.ktor.client.request.setBody import io.ktor.http.HttpStatusCode import io.ktor.server.application.call import io.ktor.server.auth.authenticate @@ -12,104 +7,50 @@ import io.ktor.server.response.respond import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.route -import no.nav.paw.bekreftelse.api.domain.BekreftelseRequest -import no.nav.paw.bekreftelse.api.domain.TilgjengeligBekreftelserResponse -import no.nav.paw.bekreftelse.api.domain.TilgjengeligeBekreftelserRequest -import no.nav.paw.bekreftelse.api.domain.toResponse -import no.nav.paw.bekreftelse.api.kafka.BekreftelseProducer -import no.nav.paw.bekreftelse.api.kafka.InternState -import no.nav.paw.bekreftelse.api.kafka.createMelding +import no.nav.paw.bekreftelse.api.model.BekreftelseRequest +import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest import no.nav.paw.bekreftelse.api.services.AutorisasjonService -import no.nav.paw.bekreftelse.api.utils.logger +import no.nav.paw.bekreftelse.api.services.BekreftelseService import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient import no.nav.poao_tilgang.client.TilgangType -import org.apache.kafka.common.serialization.Serdes -import org.apache.kafka.streams.KafkaStreams -import org.apache.kafka.streams.KeyQueryMetadata -import org.apache.kafka.streams.state.ReadOnlyKeyValueStore fun Route.bekreftelseRoutes( kafkaKeyClient: KafkaKeysClient, - stateStoreName: String, - bekreftelseStateStore: ReadOnlyKeyValueStore, - kafkaStreams: KafkaStreams, - httpClient: HttpClient, - bekreftelseProducer: BekreftelseProducer, + bekreftelseService: BekreftelseService, autorisasjonService: AutorisasjonService ) { route("/api/v1") { authenticate("tokenx", "azure") { - post("/tilgjengelige-rapporteringer") { request -> + post("/tilgjengelige-bekreftelser") { request -> with(requestScope(request.identitetsnummer, autorisasjonService, kafkaKeyClient, TilgangType.LESE)) { val arbeidssoekerId = this - bekreftelseStateStore - .get(arbeidssoekerId) - ?.tilgjendeligeBekreftelser - ?.toResponse() - ?.let { rapporteringerTilgjengelig -> - logger.info("Fant ${rapporteringerTilgjengelig.size} rapporteringer") - return@post call.respond(HttpStatusCode.OK, rapporteringerTilgjengelig) - } + val bearerToken = requireNotNull(call.request.headers["Authorization"]) { + "Authorization header is missing" + } - val metadata = kafkaStreams.queryMetadataForKey( - stateStoreName, arbeidssoekerId, Serdes.Long().serializer() - ) + val tilgjengeligeBekreftelser = bekreftelseService + .finnTilgjengeligBekreftelser(bearerToken, arbeidssoekerId, request) - if (metadata == null || metadata == KeyQueryMetadata.NOT_AVAILABLE) { - logger.info("Fant ikke metadata for arbeidsoeker, $metadata") - return@post call.respond(HttpStatusCode.OK, emptyList()) - } else { - val response = httpClient.post( - "http://${ - metadata.activeHost().host() - }/api/v1/tilgjengelige-rapporteringer" - ) { - call.request.headers["Authorization"]?.let { bearerAuth(it) } - setBody(request) - } - return@post call.respond(response.status, response.body()) - } + call.respond(HttpStatusCode.OK, tilgjengeligeBekreftelser) + + // TODO Exception handling } } - post("/rapportering") { rapportering -> - with( - requestScope( - rapportering.identitetsnummer, - autorisasjonService, - kafkaKeyClient, - TilgangType.SKRIVE - ) - ) { - val arbeidsoekerId = this + post("/bekreftelse") { request -> + with(requestScope(request.identitetsnummer, autorisasjonService, kafkaKeyClient, TilgangType.SKRIVE)) { + val arbeidssoekerId = this - bekreftelseStateStore - .get(arbeidsoekerId) - ?.tilgjendeligeBekreftelser - ?.firstOrNull { it.bekreftelseId == rapportering.bekreftelseId } - ?.let { - logger.info("Rapportering med id ${rapportering.bekreftelseId} funnet") - val rapporteringsMelding = createMelding(it, rapportering) - bekreftelseProducer.produceMessage(arbeidsoekerId, rapporteringsMelding) + val bearerToken = requireNotNull(call.request.headers["Authorization"]) { + "Authorization header is missing" + } - return@post call.respond(HttpStatusCode.OK) - } + bekreftelseService.mottaBekreftelse(bearerToken, arbeidssoekerId, request) - val metadata = kafkaStreams.queryMetadataForKey( - stateStoreName, arbeidsoekerId, Serdes.Long().serializer() - ) + call.respond(HttpStatusCode.OK) - if (metadata == null || metadata == KeyQueryMetadata.NOT_AVAILABLE) { - logger.info("Fant ikke metadata for arbeidsoeker, $metadata") - return@post call.respond(HttpStatusCode.NotFound) - } else { - val response = httpClient.post("http://${metadata.activeHost().host()}/api/v1/rapportering") { - call.request.headers["Authorization"]?.let { bearerAuth(it) } - setBody(rapportering) - } - return@post call.respond(response.status) - } + // TODO Exception handling } } } diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/HealthRoutes.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/HealthRoutes.kt deleted file mode 100644 index f8cd444e..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/HealthRoutes.kt +++ /dev/null @@ -1,22 +0,0 @@ -package no.nav.paw.bekreftelse.api.routes - -import io.ktor.server.application.call -import io.ktor.server.response.respond -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.micrometer.prometheusmetrics.PrometheusMeterRegistry -import no.nav.paw.bekreftelse.api.Health - -fun Routing.healthRoutes(prometheusMeterRegistry: PrometheusMeterRegistry, health: Health) { - get("/internal/isAlive") { - val status = health.alive() - call.respond(status.code, status.message) - } - get("/internal/isReady") { - val status = health.ready() - call.respond(status.code, status.message) - } - get("/internal/metrics") { - call.respond(prometheusMeterRegistry.scrape()) - } -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/MetricsRoutes.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/MetricsRoutes.kt new file mode 100644 index 00000000..1d49f855 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/MetricsRoutes.kt @@ -0,0 +1,13 @@ +package no.nav.paw.bekreftelse.api.routes + +import io.ktor.server.application.call +import io.ktor.server.response.respond +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry + +fun Routing.metricsRoutes(prometheusMeterRegistry: PrometheusMeterRegistry) { + get("/internal/metrics") { + call.respond(prometheusMeterRegistry.scrape()) + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt new file mode 100644 index 00000000..a7705c8d --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt @@ -0,0 +1,234 @@ +package no.nav.paw.bekreftelse.api.services + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.bearerAuth +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import no.nav.paw.bekreftelse.api.config.ApplicationConfig +import no.nav.paw.bekreftelse.api.kafka.BekreftelseProducer +import no.nav.paw.bekreftelse.api.kafka.createMelding +import no.nav.paw.bekreftelse.api.model.BekreftelseRequest +import no.nav.paw.bekreftelse.api.model.InternState +import no.nav.paw.bekreftelse.api.model.TilgjengeligBekreftelse +import no.nav.paw.bekreftelse.api.model.TilgjengeligBekreftelserResponse +import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest +import no.nav.paw.bekreftelse.api.model.toResponse +import no.nav.paw.bekreftelse.api.utils.logger +import org.apache.kafka.common.serialization.Serdes +import org.apache.kafka.streams.KafkaStreams +import org.apache.kafka.streams.KeyQueryMetadata +import org.apache.kafka.streams.StoreQueryParameters +import org.apache.kafka.streams.state.QueryableStoreTypes +import org.apache.kafka.streams.state.ReadOnlyKeyValueStore +import java.time.Duration +import java.time.Instant +import java.util.* + +interface BekreftelseService { + + suspend fun finnTilgjengeligBekreftelser( + bearerToken: String, + arbeidssoekerId: Long, + request: TilgjengeligeBekreftelserRequest + ): TilgjengeligBekreftelserResponse + + suspend fun mottaBekreftelse( + bearerToken: String, + arbeidssoekerId: Long, + request: BekreftelseRequest + ) +} + +// TODO Slett når vi har ferdig Kafka-logikk +class BekreftelseServiceMock : BekreftelseService { + + private var tilgjengeligBekreftelser = mutableMapOf() + private val fnr1 = "17830348441" + private val fnr2 = "19519238708" + private val fnr3 = "02837797848" + private val fnr4 = "16868598968" + private val fnr5 = "28878098821" + private val periodeId1 = UUID.fromString("84201f96-363b-4aab-a589-89fa4b9b1feb") + private val periodeId2 = UUID.fromString("ec6b5a10-b67c-42c1-b6e7-a642c36bd78e") + private val periodeId3 = UUID.fromString("44a4375c-b7ab-40ea-83f5-0eb9869925eb") + private val periodeId4: UUID = UUID.fromString("bbf3e9eb-6d7b-465b-bf79-ae6c82cf1ddd") + private val periodeId5: UUID = UUID.fromString("6ea57aec-353c-4df5-935f-9bead8afb221") + private val bekreftelseId1: UUID = UUID.fromString("f45ffbf3-e4d5-49fd-b5b7-17aaee478dfc") + private val bekreftelseId2: UUID = UUID.fromString("0cae8890-5500-4f5f-8fc1-9a0aae3b35a0") + private val bekreftelseId3: UUID = UUID.fromString("4f5e7f5c-1fe3-4b27-a07b-34ff9f4ea23f") + private val bekreftelseId4a: UUID = UUID.fromString("47e5c02d-abab-4e75-951c-db6c985901e4") + private val bekreftelseId4b: UUID = UUID.fromString("77322685-80db-41db-b79f-86915a9a5d9a") + private val bekreftelseId5a: UUID = UUID.fromString("992d5363-bab4-4b1d-987e-3e8eb4db3f64") + private val bekreftelseId5b: UUID = UUID.fromString("9777408c-938d-41e6-b9fd-5177120695d6") + + init { + tilgjengeligBekreftelser[fnr1] = listOf( + TilgjengeligBekreftelse(periodeId1, bekreftelseId1, pastInstant(), futureInstant()) + ) + tilgjengeligBekreftelser[fnr2] = listOf( + TilgjengeligBekreftelse(periodeId2, bekreftelseId2, pastInstant(), futureInstant()) + ) + tilgjengeligBekreftelser[fnr3] = listOf( + TilgjengeligBekreftelse(periodeId3, bekreftelseId3, pastInstant(), futureInstant()) + ) + val fra4a = pastInstant() + val fra4b = fra4a.minus(pastDuration()) + tilgjengeligBekreftelser[fnr4] = listOf( + TilgjengeligBekreftelse(periodeId4, bekreftelseId4a, fra4a, futureInstant()), + TilgjengeligBekreftelse(periodeId4, bekreftelseId4b, pastInstant(), fra4b) + ) + val fra5a = pastInstant() + val fra5b = fra5a.minus(pastDuration()) + tilgjengeligBekreftelser[fnr5] = listOf( + TilgjengeligBekreftelse(periodeId5, bekreftelseId5a, fra5a, futureInstant()), + TilgjengeligBekreftelse(periodeId5, bekreftelseId5b, fra5b, fra5a) + ) + } + + private fun pastInstant(): Instant { + return Instant.now().minus(pastDuration()) + } + + private fun futureInstant(): Instant { + return Instant.now().minus(futureDuration()) + } + + private fun pastDuration(): Duration { + val days = Random().nextLong(10, 90) + return Duration.ofDays(days) + } + + private fun futureDuration(): Duration { + val days = Random().nextLong(0, 90) + return Duration.ofDays(days) + } + + override suspend fun finnTilgjengeligBekreftelser( + bearerToken: String, + arbeidssoekerId: Long, + request: TilgjengeligeBekreftelserRequest + ): TilgjengeligBekreftelserResponse { + val key = request.identitetsnummer ?: arbeidssoekerId.toString() + return tilgjengeligBekreftelser[key] ?: emptyList() + } + + override suspend fun mottaBekreftelse( + bearerToken: String, + arbeidssoekerId: Long, + request: BekreftelseRequest + ) { + val key = request.identitetsnummer ?: arbeidssoekerId.toString() + val eksisterende = tilgjengeligBekreftelser[key] + if (eksisterende != null) { + val oppdatert = eksisterende.filter { it.bekreftelseId != request.bekreftelseId } + tilgjengeligBekreftelser[key] = oppdatert + } + } +} + +class BekreftelseServiceImpl( + private val applicationConfig: ApplicationConfig, + private val httpClient: HttpClient, + private val kafkaStreams: KafkaStreams, + private val bekreftelseProducer: BekreftelseProducer, +) : BekreftelseService { + private var internStateStore: ReadOnlyKeyValueStore? = null + + private fun getInternStateStore(): ReadOnlyKeyValueStore { + if (!kafkaStreams.state().isRunningOrRebalancing) { + throw IllegalStateException("Kafka Streams kjører ikke") + } + if (internStateStore == null) { + internStateStore = kafkaStreams.store( + StoreQueryParameters.fromNameAndType( + applicationConfig.kafkaTopology.internStateStoreName, + QueryableStoreTypes.keyValueStore() + ) + ) + } + return checkNotNull(internStateStore) { "Intern state store er ikke initiert" } + } + + override suspend fun finnTilgjengeligBekreftelser( + bearerToken: String, + arbeidssoekerId: Long, + request: TilgjengeligeBekreftelserRequest + ): TilgjengeligBekreftelserResponse { + val internState = getInternStateStore().get(arbeidssoekerId) + + if (internState != null) { + logger.info("Fant ${internState.tilgjendeligeBekreftelser.size} tilgjengelige bekreftelser") + return internState.tilgjendeligeBekreftelser.toResponse() + } else { + return finnTilgjengeligBekreftelserFraAnnenNode(bearerToken, arbeidssoekerId, request) + } + } + + override suspend fun mottaBekreftelse( + bearerToken: String, + arbeidssoekerId: Long, + request: BekreftelseRequest + ) { + val internState = getInternStateStore().get(arbeidssoekerId) + + if (internState != null) { + internState.tilgjendeligeBekreftelser + .firstOrNull { it.bekreftelseId == request.bekreftelseId } + ?.let { + logger.info("Rapportering med id ${request.bekreftelseId} funnet") + val rapporteringsMelding = createMelding(it, request) + bekreftelseProducer.produceMessage(arbeidssoekerId, rapporteringsMelding) + } + } else { + sendBekreftelseTilAnnenNode(bearerToken, arbeidssoekerId, request) + } + } + + private suspend fun finnTilgjengeligBekreftelserFraAnnenNode( + bearerToken: String, + arbeidssoekerId: Long, + request: TilgjengeligeBekreftelserRequest + ): TilgjengeligBekreftelserResponse { + val metadata = kafkaStreams.queryMetadataForKey( + applicationConfig.kafkaTopology.internStateStoreName, arbeidssoekerId, Serdes.Long().serializer() + ) + + if (metadata == null || metadata == KeyQueryMetadata.NOT_AVAILABLE) { + logger.info("Fant ikke metadata for arbeidsoeker, $metadata") + return emptyList() + } else { + val response = httpClient.post( + "http://${ + metadata.activeHost().host() + }/api/v1/tilgjengelige-rapporteringer" + ) { + bearerAuth(bearerToken) + setBody(request) + } + // TODO Error handling + return response.body() + } + } + + private suspend fun sendBekreftelseTilAnnenNode( + bearerToken: String, + arbeidssoekerId: Long, + request: BekreftelseRequest + ) { + val metadata = kafkaStreams.queryMetadataForKey( + applicationConfig.kafkaTopology.internStateStoreName, arbeidssoekerId, Serdes.Long().serializer() + ) + + if (metadata == null || metadata == KeyQueryMetadata.NOT_AVAILABLE) { + logger.info("Fant ikke metadata for arbeidsoeker, $metadata") + // TODO Not found exception + } else { + val response = httpClient.post("http://${metadata.activeHost().host()}/api/v1/rapportering") { + bearerAuth(bearerToken) + setBody(request) + } + // TODO Error handling + } + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Jackson.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Jackson.kt new file mode 100644 index 00000000..45e22f76 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Jackson.kt @@ -0,0 +1,31 @@ +package no.nav.paw.bekreftelse.api.utils + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.KotlinFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.kotlinModule + +val buildObjectMapper: ObjectMapper + get() = jacksonObjectMapper().apply { + configureJackson() + } + +fun ObjectMapper.configureJackson() { + setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) + disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) + registerModule(JavaTimeModule()) + kotlinModule { + withReflectionCacheSize(512) + disable(KotlinFeature.NullIsSameAsDefault) + disable(KotlinFeature.SingletonSupport) + disable(KotlinFeature.StrictNullChecks) + enable(KotlinFeature.NullToEmptyCollection) + enable(KotlinFeature.NullToEmptyMap) + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/KafkaSerialization.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/KafkaSerialization.kt new file mode 100644 index 00000000..f61ce85b --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/KafkaSerialization.kt @@ -0,0 +1,55 @@ +package no.nav.paw.bekreftelse.api.utils + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde +import no.nav.paw.bekreftelse.api.model.InternState +import no.nav.paw.bekreftelse.melding.v1.Bekreftelse +import no.nav.paw.config.env.NaisEnv +import no.nav.paw.config.env.currentNaisEnv +import org.apache.kafka.common.serialization.Deserializer +import org.apache.kafka.common.serialization.Serde +import org.apache.kafka.common.serialization.Serializer + +inline fun buildJsonSerializer(naisEnv: NaisEnv, objectMapper: ObjectMapper) = object : Serializer { + override fun serialize(topic: String?, data: T): ByteArray { + if (data == null) return byteArrayOf() + try { + return objectMapper.writeValueAsBytes(data) + } catch (e: Exception) { + if (naisEnv == NaisEnv.ProdGCP && e is JsonProcessingException) e.clearLocation() + throw e + } + } +} + +inline fun buildJsonDeserializer(naisEnv: NaisEnv, objectMapper: ObjectMapper) = object : Deserializer { + override fun deserialize(topic: String?, data: ByteArray?): T? { + if (data == null) return null + try { + return objectMapper.readValue(data) + } catch (e: Exception) { + if (naisEnv == NaisEnv.ProdGCP && e is JsonProcessingException) e.clearLocation() + throw e + } + } +} + +inline fun buildJsonSerde(naisEnv: NaisEnv, objectMapper: ObjectMapper) = object : Serde { + override fun serializer(): Serializer { + return buildJsonSerializer(naisEnv, objectMapper) + } + + override fun deserializer(): Deserializer { + return buildJsonDeserializer(naisEnv, objectMapper) + } +} + +inline fun buildJsonSerde(): Serde { + return buildJsonSerde(currentNaisEnv, buildObjectMapper) +} + +fun buildInternStateSerde() = buildJsonSerde() + +fun buildBekreftelseSerde() = SpecificAvroSerde() diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt index 7f4ebd60..22d29f80 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt @@ -20,7 +20,7 @@ fun auditLogMelding( melding: String, ): String = CefMessage.builder() - .applicationName("paw-rapportering-api") // TODO: fra config + .applicationName("paw-arbeidssoeker-bekreftelse-api") // TODO: fra config .event(if (tilgangType == TilgangType.LESE) CefMessageEvent.ACCESS else CefMessageEvent.UPDATE) .name("Sporingslogg") .severity(CefMessageSeverity.INFO) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/ObjectMapper.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/ObjectMapper.kt deleted file mode 100644 index e2b1f631..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/ObjectMapper.kt +++ /dev/null @@ -1,23 +0,0 @@ -package no.nav.paw.bekreftelse.api.utils - -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.KotlinFeature -import com.fasterxml.jackson.module.kotlin.KotlinModule - -object JsonUtil { - val objectMapper: ObjectMapper = ObjectMapper() - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .registerModules( - KotlinModule.Builder() - .withReflectionCacheSize(512) - .configure(KotlinFeature.NullToEmptyCollection, true) - .configure(KotlinFeature.NullToEmptyMap, true) - .configure(KotlinFeature.NullIsSameAsDefault, false) - .configure(KotlinFeature.SingletonSupport, false) - .configure(KotlinFeature.StrictNullChecks, false) - .build(), - JavaTimeModule() - ) -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/local/application_config.toml b/apps/bekreftelse-api/src/main/resources/local/application_config.toml index 9dd62d4f..eb39eb4b 100644 --- a/apps/bekreftelse-api/src/main/resources/local/application_config.toml +++ b/apps/bekreftelse-api/src/main/resources/local/application_config.toml @@ -1,30 +1,45 @@ +[kafkaTopology] applicationIdSuffix = "v1" -producerId = "paw-bekreftelse-api" -bekreftelseTopic = "paw.bekreftelse-v1" -bekreftelseHendelseLoggTopic = "paw.bekreftelse-hendelse-logg-v1" -bekreftelseStateStoreName = "bekreftelseStateStore" -hostname = "${HOSTNAME}" +producerId = "paw-arbeidssoeker-bekreftelse-api" +bekreftelseTopic = "paw.arbeidssoker-bekreftelse-v1" +bekreftelseHendelsesloggTopic = "paw.arbeidssoker-bekreftelse-hendelseslogg-v1" +internStateStoreName = "internStateStore" [[authProviders]] name = "tokenx" discoveryUrl = "http://localhost:8081/default/.well-known/openid-configuration" tokenEndpointUrl = "http://localhost:8081/default/token" -clientId = "paw-bekreftelse-api" +clientId = "paw-arbeidssoeker-bekreftelse-api" [authProviders.claims] -map = [ "acr=Level4", "acr=idporten-loa-high" ] +map = ["acr=Level4", "acr=idporten-loa-high"] combineWithOr = true [[authProviders]] name = "azure" discoveryUrl = "http://localhost:8081/default/.well-known/openid-configuration" tokenEndpointUrl = "http://localhost:8081/default/token" -clientId = "paw-rapportering-api" +clientId = "paw-arbeidssoeker-bekreftelse-api" [authProviders.claims] -map = [ "NAVident" ] +map = ["NAVident"] combineWithOr = false +[azureM2M] +tokenEndpointUrl = "http://localhost:8081/default/token" +clientId = "paw-arbeidssoeker-bekreftelse-api" + [poaoClientConfig] url = "http://localhost:8090/poao-tilgang/" -scope = "api://test.test.poao-tilgang/.default" \ No newline at end of file +scope = "api://test.test.poao-tilgang/.default" + +[kafkaKeysClient] +url = "http://localhost:8090/kafka-keys" +scope = "api://test.test.kafka-keys/.default" + +[kafkaClients] +brokers = "localhost:9092" +applicationIdPrefix = "paw-arbeidssoeker-bekreftelse-api" + +[kafkaClients.schemaRegistry] +url = "http://localhost:8082" diff --git a/apps/bekreftelse-api/src/main/resources/local/azure_m2m_key_config.toml b/apps/bekreftelse-api/src/main/resources/local/azure_m2m_key_config.toml deleted file mode 100644 index 3a45f041..00000000 --- a/apps/bekreftelse-api/src/main/resources/local/azure_m2m_key_config.toml +++ /dev/null @@ -1,2 +0,0 @@ -tokenEndpointUrl = "http://localhost:8081/default/token" -clientId = "paw-arbeidssoekerregisteret-utgang-pdl" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/local/kafka_configuration_schemareg.toml b/apps/bekreftelse-api/src/main/resources/local/kafka_configuration_schemareg.toml deleted file mode 100644 index 0b52f870..00000000 --- a/apps/bekreftelse-api/src/main/resources/local/kafka_configuration_schemareg.toml +++ /dev/null @@ -1,3 +0,0 @@ -brokers = "localhost:9092" -[schemaRegistry] -url = "http://localhost:8082" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/local/kafka_key_generator_client_config.toml b/apps/bekreftelse-api/src/main/resources/local/kafka_key_generator_client_config.toml deleted file mode 100644 index caeec4fd..00000000 --- a/apps/bekreftelse-api/src/main/resources/local/kafka_key_generator_client_config.toml +++ /dev/null @@ -1,2 +0,0 @@ -url = "MOCK" -scope = "api://../.default" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/local/kafka_streams_configuration_schemareg.toml b/apps/bekreftelse-api/src/main/resources/local/kafka_streams_configuration_schemareg.toml deleted file mode 100644 index dc69514a..00000000 --- a/apps/bekreftelse-api/src/main/resources/local/kafka_streams_configuration_schemareg.toml +++ /dev/null @@ -1,4 +0,0 @@ -brokers = "localhost:9092" -applicationIdPrefix = "paw-rapportering-v1" -[schemaRegistry] -url = "http://localhost:8082" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/local/server_config.toml b/apps/bekreftelse-api/src/main/resources/local/server_config.toml new file mode 100644 index 00000000..5f9044d0 --- /dev/null +++ b/apps/bekreftelse-api/src/main/resources/local/server_config.toml @@ -0,0 +1,6 @@ +port = 8080 +callGroupSize = 16 +workerGroupSize = 8 +connectionGroupSize = 8 +gracePeriodMillis = 300 +timeoutMillis = 300 \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/logback.xml b/apps/bekreftelse-api/src/main/resources/logback.xml index 8c0b3bad..13ca21f2 100644 --- a/apps/bekreftelse-api/src/main/resources/logback.xml +++ b/apps/bekreftelse-api/src/main/resources/logback.xml @@ -10,7 +10,6 @@ - /secure-logs/secure.log @@ -26,9 +25,17 @@ - + + + + + + + + + @@ -39,6 +46,5 @@ - diff --git a/apps/bekreftelse-api/src/main/resources/nais/application_config.toml b/apps/bekreftelse-api/src/main/resources/nais/application_config.toml index a752d3e7..2a2ab948 100644 --- a/apps/bekreftelse-api/src/main/resources/nais/application_config.toml +++ b/apps/bekreftelse-api/src/main/resources/nais/application_config.toml @@ -1,9 +1,9 @@ +[kafkaTopology] applicationIdSuffix = "v1" -producerId = "paw-bekreftelse-api" -bekreftelseTopic = "paw.bekreftelse-v1" -bekreftelseHendelseLoggTopic = "paw.bekreftelse-hendelse-logg-v1" -bekreftelseStateStoreName = "bekreftelseStateStore" -hostname = "${HOSTNAME}" +producerId = "paw-arbeidssoeker-bekreftelse-api" +bekreftelseTopic = "paw.arbeidssoker-bekreftelse-beta-v1" +bekreftelseHendelsesloggTopic = "paw.arbeidssoker-bekreftelse-hendelseslogg-beta-v1" +internStateStoreName = "internStateStore" [[authProviders]] name = "tokenx" @@ -23,11 +23,30 @@ clientId = "${AZURE_APP_CLIENT_ID}" [authProviders.claims] map = ["NAVident"] +combineWithOr = true -[kafkaKeyGeneratorClient] -url = "http://paw-kafka-key-generator/api/v2/hentEllerOpprett" -scope = "${KAFKA_KEYS_SCOPE}" +[azureM2M] +tokenEndpointUrl = "${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT}" +clientId = "${AZURE_APP_CLIENT_ID}" [poaoClientConfig] url = "http://poao-tilgang.poao.svc.cluster.local" -scope = "api://${NAIS_CLUSTER_NAME}.poao.poao-tilgang/.default" \ No newline at end of file +scope = "api://${NAIS_CLUSTER_NAME}.poao.poao-tilgang/.default" + +[kafkaKeysClient] +url = "http://paw-kafka-key-generator/api/v2/hentEllerOpprett" +scope = "api://${NAIS_CLUSTER_NAME}.paw.paw-kafka-key-generator/.default" + +[kafkaClients] +brokers = "${KAFKA_BROKERS}" +applicationIdPrefix = "${KAFKA_STREAMS_APPLICATION_ID}" + +[kafkaClients.authentication] +keystorePath = "${KAFKA_KEYSTORE_PATH}" +truststorePath = "${KAFKA_TRUSTSTORE_PATH}" +credstorePassword = "${KAFKA_CREDSTORE_PASSWORD}" + +[kafkaClients.schemaRegistry] +url = "${KAFKA_SCHEMA_REGISTRY}" +username = "${KAFKA_SCHEMA_REGISTRY_USER}" +password = "${KAFKA_SCHEMA_REGISTRY_PASSWORD}" diff --git a/apps/bekreftelse-api/src/main/resources/nais/azure_m2m_key_config.toml b/apps/bekreftelse-api/src/main/resources/nais/azure_m2m_key_config.toml deleted file mode 100644 index b6a0195e..00000000 --- a/apps/bekreftelse-api/src/main/resources/nais/azure_m2m_key_config.toml +++ /dev/null @@ -1,2 +0,0 @@ -tokenEndpointUrl = "${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT}" -clientId = "${AZURE_APP_CLIENT_ID}" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/nais/kafka_key_generator_client_config.toml b/apps/bekreftelse-api/src/main/resources/nais/kafka_key_generator_client_config.toml deleted file mode 100644 index e83e543b..00000000 --- a/apps/bekreftelse-api/src/main/resources/nais/kafka_key_generator_client_config.toml +++ /dev/null @@ -1,2 +0,0 @@ -url = "http://paw-kafka-key-generator/api/v2/hentEllerOpprett" -scope = "${KAFKA_KEYS_SCOPE}" \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/resources/nais/server_config.toml b/apps/bekreftelse-api/src/main/resources/nais/server_config.toml new file mode 100644 index 00000000..5f9044d0 --- /dev/null +++ b/apps/bekreftelse-api/src/main/resources/nais/server_config.toml @@ -0,0 +1,6 @@ +port = 8080 +callGroupSize = 16 +workerGroupSize = 8 +connectionGroupSize = 8 +gracePeriodMillis = 300 +timeoutMillis = 300 \ No newline at end of file diff --git a/apps/bekreftelse-tjeneste/build.gradle.kts b/apps/bekreftelse-tjeneste/build.gradle.kts index a45ab7f0..910e9c5c 100644 --- a/apps/bekreftelse-tjeneste/build.gradle.kts +++ b/apps/bekreftelse-tjeneste/build.gradle.kts @@ -11,8 +11,8 @@ dependencies { implementation(project(":lib:kafka-key-generator-client")) implementation(project(":domain:main-avro-schema")) implementation(project(":domain:bekreftelse-interne-hendelser")) - implementation(project(":domain:bekreftelsesansvar-schema")) - implementation(project(":domain:bekreftelsesmelding-schema")) + implementation(project(":domain:bekreftelsesansvar-avro-schema")) + implementation(project(":domain:bekreftelsesmelding-avro-schema")) implementation(orgApacheKafka.kafkaStreams) implementation(jackson.datatypeJsr310) implementation(jackson.kotlin) diff --git a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/AnsvarTopology.kt b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/AnsvarTopology.kt index bc786d2d..73853648 100644 --- a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/AnsvarTopology.kt +++ b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/AnsvarTopology.kt @@ -1,7 +1,7 @@ package no.nav.paw.bekretelsetjeneste import no.nav.paw.config.kafka.streams.genericProcess -import no.nav.paw.rapportering.ansvar.v1.AnsvarEndret +import no.nav.paw.bekreftelse.ansvar.v1.AnsvarEndret import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelse import org.apache.kafka.streams.StreamsBuilder diff --git a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/RapportingsMeldingTopology.kt b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/RapportingsMeldingTopology.kt index 5ec5f383..70e12797 100644 --- a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/RapportingsMeldingTopology.kt +++ b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/RapportingsMeldingTopology.kt @@ -9,7 +9,6 @@ import no.nav.paw.bekretelsetjeneste.tilstand.Tilstand import no.nav.paw.bekretelsetjeneste.tilstand.Tilstand.KlarForUtfylling import no.nav.paw.bekretelsetjeneste.tilstand.Tilstand.VenterSvar import no.nav.paw.config.kafka.streams.genericProcess -import no.nav.paw.rapportering.melding.v1.Melding import org.apache.kafka.streams.StreamsBuilder import org.apache.kafka.streams.processor.api.Record import org.slf4j.LoggerFactory @@ -17,8 +16,8 @@ import java.util.* context(ApplicationConfiguration, ApplicationContext) fun StreamsBuilder.processBekreftelseMeldingTopic() { - stream(bekreftelseTopic) - .genericProcess( + stream(bekreftelseTopic) + .genericProcess( name = "meldingMottatt", stateStoreName ) { record -> @@ -52,7 +51,7 @@ fun StreamsBuilder.processBekreftelseMeldingTopic() { } } -fun behandleGyldigSvar(arbeidssoekerId: Long, record: Record, bekreftelse: Bekreftelse): Pair, Bekreftelse> { +fun behandleGyldigSvar(arbeidssoekerId: Long, record: Record, bekreftelse: Bekreftelse): Pair, Bekreftelse> { val oppdatertBekreftelse = bekreftelse.copy(tilstand = Tilstand.Levert) val baOmAaAvslutte = if (!record.value().svar.vilFortsetteSomArbeidssoeker) { BaOmAaAvsluttePeriode( diff --git a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/Startup.kt b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/Startup.kt index 37ee45dc..a11a306c 100644 --- a/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/Startup.kt +++ b/apps/bekreftelse-tjeneste/src/main/kotlin/no/nav/paw/bekretelsetjeneste/Startup.kt @@ -19,9 +19,9 @@ const val APPLICATION_ID_SUFFIX = "beta" fun main() { val applicationConfiguration = ApplicationConfiguration( periodeTopic = "paw.arbeidssokerperioder-v1", - ansvarsTopic = "paw.bekreftelse-ansvar-v1", - bekreftelseTopic = "paw.bekreftelse-svar-v1", - bekreftelseHendelseloggTopic = "paw.bekreftelse-hendelselogg-v1", + ansvarsTopic = "paw.arbeidssoker-bekreftelse-ansvar-beta-v1", + bekreftelseTopic = "paw.arbeidssoker-bekreftelse-beta-v1", + bekreftelseHendelseloggTopic = "paw.arbeidssoker-bekreftelse-hendelseslogg-beta-v1", stateStoreName = "bekreftelse", punctuateInterval = Duration.ofMinutes(5) ) diff --git a/apps/bekreftelse-tjeneste/src/test/kotlin/no/nav/paw/bekretelsetjeneste/ApplicationTestContext.kt b/apps/bekreftelse-tjeneste/src/test/kotlin/no/nav/paw/bekretelsetjeneste/ApplicationTestContext.kt index 2963eaea..f95ce5a3 100644 --- a/apps/bekreftelse-tjeneste/src/test/kotlin/no/nav/paw/bekretelsetjeneste/ApplicationTestContext.kt +++ b/apps/bekreftelse-tjeneste/src/test/kotlin/no/nav/paw/bekretelsetjeneste/ApplicationTestContext.kt @@ -4,13 +4,13 @@ import io.confluent.kafka.schemaregistry.testutil.MockSchemaRegistry import io.confluent.kafka.serializers.KafkaAvroSerializerConfig import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde import no.nav.paw.arbeidssokerregisteret.api.v1.Periode -import no.nav.paw.kafkakeygenerator.client.KafkaKeysResponse -import no.nav.paw.kafkakeygenerator.client.inMemoryKafkaKeysMock -import no.nav.paw.bekretelsetjeneste.tilstand.InternTilstandSerde -import no.nav.paw.rapportering.ansvar.v1.AnsvarEndret +import no.nav.paw.bekreftelse.ansvar.v1.AnsvarEndret import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelse import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelseSerde -import no.nav.paw.rapportering.melding.v1.Melding +import no.nav.paw.bekreftelse.melding.v1.Bekreftelse +import no.nav.paw.bekretelsetjeneste.tilstand.InternTilstandSerde +import no.nav.paw.kafkakeygenerator.client.KafkaKeysResponse +import no.nav.paw.kafkakeygenerator.client.inMemoryKafkaKeysMock import org.apache.avro.specific.SpecificRecord import org.apache.kafka.common.serialization.Serde import org.apache.kafka.common.serialization.Serdes @@ -25,7 +25,7 @@ import java.util.* class ApplicationTestContext { val ansvarsTopicSerde: Serde = opprettSerde() - val rapporteringMeldingSerde: Serde = opprettSerde() + val bekreftelseSerde: Serde = opprettSerde() val periodeTopicSerde: Serde = opprettSerde() val hendelseLoggSerde: Serde = BekreftelseHendelseSerde() val applicationConfiguration = ApplicationConfiguration( @@ -72,7 +72,7 @@ class ApplicationTestContext { val rapporteringsTopic = testDriver.createInputTopic( applicationConfiguration.bekreftelseTopic, Serdes.Long().serializer(), - rapporteringMeldingSerde.serializer() + bekreftelseSerde.serializer() ) val hendelseLoggTopic = testDriver.createOutputTopic( diff --git a/docker/kafka/docker-compose.yaml b/docker/kafka/docker-compose.yaml new file mode 100644 index 00000000..f7f6aabe --- /dev/null +++ b/docker/kafka/docker-compose.yaml @@ -0,0 +1,110 @@ +### SERVICES ### +services: + # Kafka + kafka: + image: confluentinc/cp-server:7.6.1 + hostname: kafka + ports: + - "9092:9092" + # - "9101:9101" + environment: + KAFKA_NODE_ID: 1 + CLUSTER_ID: YTAzZDkwYmJjZmVmNDc3N2 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT + KAFKA_LISTENERS: CONTROLLER://kafka:29093,INTERNAL://kafka:29092,EXTERNAL://0.0.0.0:9092 + KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_METRIC_REPORTERS: io.confluent.metrics.reporter.ConfluentMetricsReporter + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_JMX_PORT: 9101 + KAFKA_JMX_HOSTNAME: localhost + KAFKA_CONFLUENT_SCHEMA_REGISTRY_URL: http://schema-registry:8082 + CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS: 1 + CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS: kafka:29092 + CONFLUENT_METRICS_ENABLE: true + CONFLUENT_SUPPORT_CUSTOMER_ID: anonymous + KAFKA_LOG_DIRS: /tmp/kraft-combined-logs + volumes: + - kafka-data:/var/lib/kafka/data + - kafka-secrets:/etc/kafka/secrets + networks: + - kafka + + # Kafka Init + init-kafka: + image: confluentinc/cp-server:7.6.1 + depends_on: + - kafka + entrypoint: [ '/bin/sh', '-c' ] + command: | + " + # blocks until kafka is reachable + kafka-topics --bootstrap-server kafka:29092 --list + + echo -e 'Creating kafka topics' + kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic paw.arbeidssokerperioder-v1 --replication-factor 1 --partitions 1 + kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic paw.opplysninger-om-arbeidssoeker-v1 --replication-factor 1 --partitions 1 + kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic paw.arbeidssoker-profilering-v1 --replication-factor 1 --partitions 1 + kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic paw.arbeidssoker-bekreftelse-v1 --replication-factor 1 --partitions 1 + kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic paw.arbeidssoker-bekreftelse-hendelseslogg-v1 --replication-factor 1 --partitions 1 + + echo -e 'Successfully created the following topics:' + kafka-topics --bootstrap-server kafka:29092 --list + " + networks: + - kafka + + # Schema Registry + schema-registry: + image: confluentinc/cp-schema-registry:7.6.1 + hostname: schema-registry + depends_on: + - kafka + ports: + - "8082:8082" + environment: + SCHEMA_REGISTRY_HOST_NAME: schema-registry + SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: 'kafka:29092' + SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8082 + volumes: + - schema-registry-secrets:/etc/schema-registry/secrets + networks: + - kafka + + # Kafka UI + kafka-ui: + image: provectuslabs/kafka-ui:latest + ports: + - "9000:8080" + environment: + DYNAMIC_CONFIG_ENABLED: "true" + KAFKA_CLUSTERS_0_NAME: main + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 + KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schema-registry:8082 + depends_on: + - kafka + - schema-registry + networks: + - kafka + +### VOLUMES ### +volumes: + kafka-data: + name: kafka-data + kafka-secrets: + name: kafka-secrets + schema-registry-secrets: + name: schema-registry-secrets + +### NETWORKS ### +networks: + kafka: + name: kafka diff --git a/docker/mocks/config/mock-oauth2-server/README.md b/docker/mocks/config/mock-oauth2-server/README.md new file mode 100644 index 00000000..9e7a631d --- /dev/null +++ b/docker/mocks/config/mock-oauth2-server/README.md @@ -0,0 +1,8 @@ +# Mock OAuth2 Server + +## Hente token + +### Hente token vha cURL +```shell +curl -H "Content-Type: application/x-www-form-urlencoded" -F "grant_type=client_credentials" -v "http://localhost:8081/default/token" +``` \ No newline at end of file diff --git a/docker/mocks/config/mock-oauth2-server/config.json b/docker/mocks/config/mock-oauth2-server/config.json new file mode 100644 index 00000000..e519de19 --- /dev/null +++ b/docker/mocks/config/mock-oauth2-server/config.json @@ -0,0 +1,35 @@ +{ + "interactiveLogin": true, + "httpServer": "NettyWrapper", + "tokenCallbacks": [ + { + "issuerId": "default", + "tokenExpiry": 3600, + "requestMappings": [ + { + "requestParam": "client_id", + "match": "paw-microfrontend-toggler", + "claims": { + "sub": "admin@paw-microfrontend-toggler", + "aud": [ + "paw-microfrontend-toggler" + ], + "acr": "idporten-loa-high" + } + }, + { + "requestParam": "client_id", + "match": "paw-arbeidssoeker-bekreftelse-api", + "claims": { + "sub": "admin@paw-arbeidssoeker-bekreftelse-api", + "aud": [ + "paw-arbeidssoeker-bekreftelse-api" + ], + "pid": "17830348441", + "acr": "idporten-loa-high" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/docker/mocks/config/wiremock/kafka-keys.json b/docker/mocks/config/wiremock/kafka-keys.json new file mode 100644 index 00000000..5f191f17 --- /dev/null +++ b/docker/mocks/config/wiremock/kafka-keys.json @@ -0,0 +1,16 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/kafka-keys" + }, + "response": { + "status": 200, + "jsonBody": { + "id": 1, + "key": 1 + }, + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/docker/mocks/config/wiremock/pdl.json b/docker/mocks/config/wiremock/pdl.json new file mode 100644 index 00000000..01327b99 --- /dev/null +++ b/docker/mocks/config/wiremock/pdl.json @@ -0,0 +1,51 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/pdl" + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "hentPerson": { + "foedsel": [ + { + "foedselsdato": "1986-11-26", + "foedselsaar": "1986" + } + ], + "opphold": [ + { + "oppholdFra": "2010-01-20", + "oppholdTil": "2022-01-20", + "type": "PERMANENT" + } + ], + "folkeregisterpersonstatus": [ + { + "forenkletStatus": "bosattEtterFolkeregisterloven" + } + ], + "bostedsadresse": [ + { + "angittFlyttedato": "2010-01-20", + "gyldigFraOgMed": "2010-01-20", + "gyldigTilOgMed": "2022-01-20", + "vegadresse": { + "kommunenummer": "0301" + }, + "matrikkeladresse": null, + "ukjentBosted" : null, + "utenlandskAdresse": null + } + ], + "innflyttingTilNorge": [], + "utflyttingFraNorge": [] + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/docker/mocks/config/wiremock/poao-tilgang.json b/docker/mocks/config/wiremock/poao-tilgang.json new file mode 100644 index 00000000..c8f2b67f --- /dev/null +++ b/docker/mocks/config/wiremock/poao-tilgang.json @@ -0,0 +1,22 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/poao-tilgang/api/v1/policy/evaluate" + }, + "response": { + "status": 200, + "jsonBody": { + "results": [ + { + "requestId": "073ee000-3e61-11ed-b878-0242ac120002", + "decision": { + "type": "PERMIT" + } + } + ] + }, + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/docker/mocks/docker-compose.yaml b/docker/mocks/docker-compose.yaml new file mode 100644 index 00000000..f4512ffa --- /dev/null +++ b/docker/mocks/docker-compose.yaml @@ -0,0 +1,29 @@ +### SERVICES ### +services: + wiremock: + image: wiremock/wiremock:3.4.2 + container_name: wiremock + ports: + - "8090:8080" + volumes: + - ./config/wiremock:/home/wiremock/mappings/ + networks: + - mocks + + mock-oauth2-server: + image: ghcr.io/navikt/mock-oauth2-server:2.1.5 + container_name: mock-oauth2-server + ports: + - "8081:8081" + environment: + SERVER_PORT: 8081 + JSON_CONFIG_PATH: /config.json + volumes: + - ./config/mock-oauth2-server/config.json:/config.json + networks: + - mocks + +### NETWORKS ### +networks: + mocks: + name: mocks diff --git a/domain/bekreftelsesansvar-schema/build.gradle.kts b/domain/bekreftelsesansvar-avro-schema/build.gradle.kts similarity index 77% rename from domain/bekreftelsesansvar-schema/build.gradle.kts rename to domain/bekreftelsesansvar-avro-schema/build.gradle.kts index 31dfdfed..da782834 100644 --- a/domain/bekreftelsesansvar-schema/build.gradle.kts +++ b/domain/bekreftelsesansvar-avro-schema/build.gradle.kts @@ -10,8 +10,8 @@ val schema by configurations.creating { } dependencies { - schema(rapportering.rapporteringsansvarSchema) - implementation(rapportering.rapporteringsansvarSchema) + schema(bekreftelse.bekreftelsesansvarSchema) + implementation(bekreftelse.bekreftelsesansvarSchema) api(apacheAvro.avro) } diff --git a/domain/bekreftelsesmelding-schema/build.gradle.kts b/domain/bekreftelsesmelding-avro-schema/build.gradle.kts similarity index 77% rename from domain/bekreftelsesmelding-schema/build.gradle.kts rename to domain/bekreftelsesmelding-avro-schema/build.gradle.kts index 15d2243e..664ea7ab 100644 --- a/domain/bekreftelsesmelding-schema/build.gradle.kts +++ b/domain/bekreftelsesmelding-avro-schema/build.gradle.kts @@ -10,8 +10,8 @@ val schema by configurations.creating { } dependencies { - schema(rapportering.rapporteringsmeldingSchema) - implementation(rapportering.rapporteringsmeldingSchema) + schema(bekreftelse.bekreftelsesmeldingSchema) + implementation(bekreftelse.bekreftelsesmeldingSchema) api(apacheAvro.avro) } diff --git a/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/HttpExceptionHandler.kt b/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/HttpExceptionHandler.kt index 3f970cee..a3f206d3 100644 --- a/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/HttpExceptionHandler.kt +++ b/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/HttpExceptionHandler.kt @@ -15,7 +15,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory private const val ERROR_TYPE_PREFIX = "PAW_" -private val logger: Logger = LoggerFactory.getLogger("paw.application.error.http") +private val logger: Logger = LoggerFactory.getLogger("no.nav.paw.logger.error.http") suspend fun ApplicationCall.handleException(throwable: T) { when (throwable) { diff --git a/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/KafkaExceptionHandler.kt b/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/KafkaExceptionHandler.kt index e4d7a8b7..1af2a960 100644 --- a/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/KafkaExceptionHandler.kt +++ b/lib/error-handling/src/main/kotlin/no/nav/paw/error/handler/KafkaExceptionHandler.kt @@ -5,9 +5,13 @@ import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler import org.slf4j.Logger import org.slf4j.LoggerFactory -private val logger: Logger = LoggerFactory.getLogger("paw.application.error.kafka") +private val logger: Logger = LoggerFactory.getLogger("no.nav.paw.logger.error.kafka") -fun KafkaStreams.withApplicationTerminatingExceptionHandler() = StreamsUncaughtExceptionHandler { throwable -> +fun KafkaStreams.withApplicationTerminatingExceptionHandler() { + this.setUncaughtExceptionHandler(createApplicationTerminatingExceptionHandler()) +} + +fun createApplicationTerminatingExceptionHandler() = StreamsUncaughtExceptionHandler { throwable -> logger.error("Kafka Streams opplevde en uventet feil", throwable) StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.SHUTDOWN_APPLICATION } \ No newline at end of file diff --git a/lib/error-handling/src/main/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListener.kt b/lib/error-handling/src/main/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListener.kt index b6f50ff1..574bc9b2 100644 --- a/lib/error-handling/src/main/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListener.kt +++ b/lib/error-handling/src/main/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListener.kt @@ -5,11 +5,18 @@ import no.nav.paw.health.model.ReadinessHealthIndicator import org.apache.kafka.streams.KafkaStreams import org.slf4j.LoggerFactory -private val logger = LoggerFactory.getLogger("paw.application.health.kafka") +private val logger = LoggerFactory.getLogger("no.nav.paw.logger.health.kafka") fun KafkaStreams.withHealthIndicatorStateListener( livenessIndicator: LivenessHealthIndicator, readinessIndicator: ReadinessHealthIndicator +) { + this.setStateListener(createHealthIndicatorStateListener(livenessIndicator, readinessIndicator)) +} + +fun createHealthIndicatorStateListener( + livenessIndicator: LivenessHealthIndicator, + readinessIndicator: ReadinessHealthIndicator ) = KafkaStreams.StateListener { newState, previousState -> when (newState) { KafkaStreams.State.CREATED -> { diff --git a/lib/error-handling/src/test/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListenerTest.kt b/lib/error-handling/src/test/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListenerTest.kt index b9a5f344..3330af75 100644 --- a/lib/error-handling/src/test/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListenerTest.kt +++ b/lib/error-handling/src/test/kotlin/no/nav/paw/health/listener/KafkaStreamsStatusListenerTest.kt @@ -2,7 +2,6 @@ package no.nav.paw.health.listener import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe -import io.mockk.mockkClass import no.nav.paw.health.model.HealthStatus import no.nav.paw.health.model.LivenessHealthIndicator import no.nav.paw.health.model.ReadinessHealthIndicator @@ -13,13 +12,12 @@ import org.apache.kafka.streams.KafkaStreams class KafkaStreamsStatusListenerTest : FreeSpec({ "Kafka Streams Status Listener skal returnere korrekt helsesjekk-status" { - val kafkaStreams = mockkClass(KafkaStreams::class) val healthIndicatorRepository = HealthIndicatorRepository() val liveness = healthIndicatorRepository.addLivenessIndicator(LivenessHealthIndicator()) val readiness = healthIndicatorRepository.addReadinessIndicator(ReadinessHealthIndicator()) - val listener = kafkaStreams.withHealthIndicatorStateListener(liveness, readiness) + val listener = createHealthIndicatorStateListener(liveness, readiness) healthIndicatorRepository.getReadinessIndicators().getAggregatedStatus() shouldBe HealthStatus.UNKNOWN healthIndicatorRepository.getLivenessIndicators().getAggregatedStatus() shouldBe HealthStatus.HEALTHY diff --git a/settings.gradle.kts b/settings.gradle.kts index 88fb906d..3e59c039 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,8 +15,8 @@ include( "lib:kafka-streams", "lib:kafka-key-generator-client", "domain:bekreftelse-interne-hendelser", - "domain:bekreftelsesansvar-schema", - "domain:bekreftelsesmelding-schema", + "domain:bekreftelsesansvar-avro-schema", + "domain:bekreftelsesmelding-avro-schema", "domain:main-avro-schema", "domain:interne-hendelser", "domain:arbeidssoekerregisteret-kotlin", @@ -66,7 +66,7 @@ dependencyResolutionManagement { val pawPdlClientVersion = "24.08.08.40-1" val pawAaregClientVersion = "24.07.04.18-1" val arbeidssokerregisteretVersion = "1.9348086045.48-1" - val rapporteringsSchemaVersion = "24.09.11.8-1" + val bekreftelseSchemaVersion = "24.09.16.1-1" //Arrow @@ -265,17 +265,17 @@ dependencyResolutionManagement { create("poao") { library("tilgangClient", "no.nav.poao-tilgang", "client").version("2024.04.29_13.59-a0ddddd36ac9") } - create("rapportering") { + create("bekreftelse") { library( - "rapporteringsansvarSchema", + "bekreftelsesansvarSchema", "no.nav.paw.arbeidssokerregisteret.api", - "rapporteringsansvar-schema" - ).version(rapporteringsSchemaVersion) + "bekreftelsesansvar-schema" + ).version(bekreftelseSchemaVersion) library( - "rapporteringsmeldingSchema", + "bekreftelsesmeldingSchema", "no.nav.paw.arbeidssokerregisteret.api", - "rapporteringsmelding-schema" - ).version(rapporteringsSchemaVersion) + "bekreftelsesmelding-schema" + ).version(bekreftelseSchemaVersion) } } }