diff options
26 files changed, 975 insertions, 174 deletions
@@ -48,6 +48,7 @@ <properties> <java.version>8</java.version> <spring-boot.version>2.1.2.RELEASE</spring-boot.version> + <spring-cloud.version>Greenwich.SR1</spring-cloud.version> <springfox.version>2.9.2</springfox.version> <immutables.version>2.7.5</immutables.version> <sdk.version>1.2.0-SNAPSHOT</sdk.version> @@ -150,6 +151,14 @@ </dependency> <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>${spring-cloud.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> diff --git a/prh-app-server/pom.xml b/prh-app-server/pom.xml index 50ee8044..8bda3f07 100644 --- a/prh-app-server/pom.xml +++ b/prh-app-server/pom.xml @@ -205,6 +205,10 @@ </dependency> <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-config</artifactId> + </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> @@ -239,6 +243,12 @@ <dependency> <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsBootstrapConfiguration.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsBootstrapConfiguration.java new file mode 100644 index 00000000..0297a67a --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsBootstrapConfiguration.java @@ -0,0 +1,57 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + + +import org.onap.dcaegen2.services.prh.configuration.CbsConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties +public class CbsBootstrapConfiguration { + + @Bean + public CbsProperties cbsProperties() { + return new CbsProperties(); + } + + @Bean + @ConditionalOnProperty(value = "cbs.enabled", matchIfMissing = true) + public CbsPropertySourceLocator cbsPropertySourceLocator( + CbsProperties cbsProperties, + CbsConfiguration cbsConfiguration) { + + return new CbsPropertySourceLocator( + cbsProperties, + new CbsJsonToPropertyMapConverter(), + new CbsClientConfigurationResolver(cbsProperties), + new CbsClientFactoryFacade(), + cbsConfiguration); + } + + @Bean + public CbsConfiguration cbsConfiguration() { + return new CbsConfiguration(); + } +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolver.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolver.java index ce4cd4ed..41389980 100644 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolver.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolver.java @@ -18,28 +18,29 @@ * ============LICENSE_END========================================================= */ -package org.onap.dcaegen2.services.prh.configuration; +package org.onap.dcaegen2.services.bootstrap; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import reactor.core.publisher.Mono; -@Component -public class CbsClientConfigurationResolver { +class CbsClientConfigurationResolver { private static final Logger LOGGER = LoggerFactory.getLogger(CbsClientConfigurationResolver.class); - private final CbsClientConfigFileReader cbsClientConfigFileReader; + private final CbsProperties cbsProperties; - public CbsClientConfigurationResolver(CbsClientConfigFileReader cbsClientConfigFileReader) { - this.cbsClientConfigFileReader = cbsClientConfigFileReader; + CbsClientConfigurationResolver(CbsProperties cbsProperties) { + this.cbsProperties = cbsProperties; } - Mono<CbsClientConfiguration> resolveCbsClientConfiguration() { - return Mono.fromSupplier(CbsClientConfiguration::fromEnvironment) - .doOnError(err -> LOGGER.warn("Failed resolving CBS client configuration from system environments", err)) - .onErrorResume(err -> cbsClientConfigFileReader.readConfig()); + CbsClientConfiguration resolveCbsClientConfiguration() { + try { + return CbsClientConfiguration.fromEnvironment(); + } catch (Exception e) { + LOGGER.warn("Failed resolving CBS client configuration from system environments: " + e); + } + LOGGER.info("Falling back to use default CBS client configuration properties"); + return cbsProperties.toCbsClientConfiguration(); } } diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReaderTest.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientFactoryFacade.java index c4cf7089..a68a35c9 100644 --- a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReaderTest.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientFactoryFacade.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * PNF-REGISTRATION-HANDLER * ================================================================================ - * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,29 +18,17 @@ * ============LICENSE_END========================================================= */ +package org.onap.dcaegen2.services.bootstrap; -package org.onap.dcaegen2.services.prh.configuration; - -import org.junit.jupiter.api.Test; +import org.jetbrains.annotations.NotNull; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; -import org.springframework.core.io.ClassRelativeResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; - -import static org.junit.jupiter.api.Assertions.*; - -class CbsClientConfigFileReaderTest { - - private ResourceLoader resourceLoader = new ClassRelativeResourceLoader(ConsulConfigurationParserTest.class); - - @Test - void shouldProvideDefaultCbsClientConfigurationLoadedFromTheFile() { - Resource configFile = resourceLoader.getResource("classpath:cbs_client_config.json"); - - CbsClientConfiguration configuration = new CbsClientConfigFileReader(configFile).readConfig().block(); +import reactor.core.publisher.Mono; - assertEquals("dcae-prh", configuration.appName()); - assertEquals("cbs", configuration.hostname()); - assertEquals(Integer.valueOf(10000), configuration.port()); +class CbsClientFactoryFacade { + @NotNull + Mono<CbsClient> createCbsClient(CbsClientConfiguration cbsClientConfiguration) { + return CbsClientFactory.createCbsClient(cbsClientConfiguration); } -}
\ No newline at end of file +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverter.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverter.java new file mode 100644 index 00000000..bf4077b9 --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverter.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.Map; +import java.util.stream.Collectors; + +class CbsJsonToPropertyMapConverter { + + private static final String CBS_CONFIG_ROOT_PROPERTY = "config"; + + Map<String, Object> convertToMap(JsonObject jsonObject) { + verifyExpectedCbsJsonFormat(jsonObject); + JsonObject config = jsonObject.getAsJsonObject(CBS_CONFIG_ROOT_PROPERTY); + return config.entrySet().stream().collect( + Collectors.toMap(Map.Entry::getKey, entry -> unpack(entry.getValue()))); + } + + private static void verifyExpectedCbsJsonFormat(JsonObject jsonObject) { + if (!jsonObject.has(CBS_CONFIG_ROOT_PROPERTY)) { + throw new IllegalArgumentException("Missing expected '" + CBS_CONFIG_ROOT_PROPERTY + "'" + + " property in json from CBS."); + } + } + + private Object unpack(JsonElement value) { + if (value.isJsonPrimitive()) { + JsonPrimitive primitiveValue = value.getAsJsonPrimitive(); + if (primitiveValue.isString()) { + return primitiveValue.getAsString(); + } + if (primitiveValue.isBoolean()) { + return primitiveValue.getAsBoolean(); + } + if (primitiveValue.isNumber()) { + return primitiveValue.getAsLong(); + } + } + return value; + } + +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsProperties.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsProperties.java new file mode 100644 index 00000000..18d4021b --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsProperties.java @@ -0,0 +1,98 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + + +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.ImmutableCbsClientConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import java.time.Duration; + +@ConfigurationProperties("cbs") +public class CbsProperties { + + private Boolean enabled; + private Duration updatesInterval; + @NestedConfigurationProperty + private RetryProperties fetchRetries = new RetryProperties(); + private String hostname; + private Integer port; + private String appName; + + CbsClientConfiguration toCbsClientConfiguration() { + return ImmutableCbsClientConfiguration.builder() + .hostname(hostname) + .port(port) + .appName(appName) + .build(); + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Duration getUpdatesInterval() { + return updatesInterval; + } + + public void setUpdatesInterval(Duration updatesInterval) { + this.updatesInterval = updatesInterval; + } + + public RetryProperties getFetchRetries() { + return fetchRetries; + } + + public void setFetchRetries(RetryProperties fetchRetries) { + this.fetchRetries = fetchRetries; + } + +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocator.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocator.java new file mode 100644 index 00000000..7b660202 --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocator.java @@ -0,0 +1,84 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + +import com.google.gson.JsonObject; +import org.onap.dcaegen2.services.prh.configuration.CbsConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.bootstrap.config.PropertySourceLocator; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; + +import java.util.Map; + +public class CbsPropertySourceLocator implements PropertySourceLocator { + private static final Logger LOGGER = LoggerFactory.getLogger(CbsPropertySourceLocator.class); + + private final CbsProperties cbsProperties; + private final CbsJsonToPropertyMapConverter cbsJsonToPropertyMapConverter; + private final CbsClientConfigurationResolver cbsClientConfigurationResolver; + private final CbsClientFactoryFacade cbsClientFactoryFacade; + private final CbsConfiguration cbsConfiguration; + + public CbsPropertySourceLocator(CbsProperties cbsProperties, + CbsJsonToPropertyMapConverter cbsJsonToPropertyMapConverter, + CbsClientConfigurationResolver cbsClientConfigurationResolver, + CbsClientFactoryFacade cbsClientFactoryFacade, + CbsConfiguration cbsConfiguration) { + this.cbsProperties = cbsProperties; + this.cbsJsonToPropertyMapConverter = cbsJsonToPropertyMapConverter; + this.cbsClientConfigurationResolver = cbsClientConfigurationResolver; + this.cbsClientFactoryFacade = cbsClientFactoryFacade; + this.cbsConfiguration = cbsConfiguration; + } + + @Override + public PropertySource<?> locate(Environment environment) { + CbsClientConfiguration cbsClientConfiguration = cbsClientConfigurationResolver.resolveCbsClientConfiguration(); + LOGGER.info("Fetching configuration from Config Binding Service @ {}:{} for {}", + cbsClientConfiguration.hostname(), cbsClientConfiguration.port(), cbsClientConfiguration.appName()); + Map<String, Object> properties = cbsClientFactoryFacade.createCbsClient(cbsClientConfiguration) + .flatMap(cbsClient -> cbsClient.get(CbsRequests.getAll(RequestDiagnosticContext.create()))) + .doOnError(e -> LOGGER.warn("Failed fetching config properties from CBS - retrying...", e)) + .retryBackoff(cbsProperties.getFetchRetries().getMaxAttempts(), + cbsProperties.getFetchRetries().getFirstBackoff(), + cbsProperties.getFetchRetries().getMaxBackoff()) + .doOnNext(this::updateCbsConfig) + .map(cbsJsonToPropertyMapConverter::convertToMap) + .block(); + return new MapPropertySource("cbs", properties); + } + + private void updateCbsConfig(JsonObject jsonObject) { + try { + cbsConfiguration.parseCBSConfig(jsonObject); + } catch (Exception e) { + LOGGER.error("Failed parsing configuration from CBS", e); + throw e; + } + } + +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/RetryProperties.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/RetryProperties.java new file mode 100644 index 00000000..44108a72 --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/RetryProperties.java @@ -0,0 +1,54 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + +import java.time.Duration; + +public class RetryProperties { + + private Integer maxAttempts = 10; + private Duration firstBackoff = Duration.ofSeconds(3); + private Duration maxBackoff = Duration.ofSeconds(15); + + public Integer getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(Integer maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public Duration getFirstBackoff() { + return firstBackoff; + } + + public void setFirstBackoff(Duration firstBackoff) { + this.firstBackoff = firstBackoff; + } + + public Duration getMaxBackoff() { + return maxBackoff; + } + + public void setMaxBackoff(Duration maxBackoff) { + this.maxBackoff = maxBackoff; + } +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/MainApp.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/MainApp.java index 3445c076..84d9fcd2 100644 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/MainApp.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/MainApp.java @@ -28,6 +28,7 @@ import org.slf4j.MDC; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; @@ -42,6 +43,7 @@ import static org.onap.dcaegen2.services.sdk.rest.services.model.logging.MdcVari @SpringBootApplication(exclude = {JacksonAutoConfiguration.class}) @Configuration @EnableScheduling +@EnableConfigurationProperties public class MainApp { public static void main(String[] args) { diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReader.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReader.java deleted file mode 100644 index f481f4ca..00000000 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * PNF-REGISTRATION-HANDLER - * ================================================================================ - * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ - -package org.onap.dcaegen2.services.prh.configuration; - -import com.google.gson.Gson; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.ImmutableCbsClientConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.stereotype.Component; -import reactor.core.publisher.Mono; - -@Component -public class CbsClientConfigFileReader { - - private static final Logger LOGGER = LoggerFactory.getLogger(CbsClientConfigFileReader.class); - - private final Resource cbsClientConfigFile; - - public CbsClientConfigFileReader(@Value("classpath:cbs_client_config.json") Resource cbsClientConfigFile) { - this.cbsClientConfigFile = cbsClientConfigFile; - } - - public Mono<CbsClientConfiguration> readConfig() { - LOGGER.debug("Loading CBS client configuration from configuration file"); - try (InputStream inputStream = cbsClientConfigFile.getInputStream()) { - CbsClientConfiguration config = new Gson().fromJson( - new InputStreamReader(inputStream, StandardCharsets.UTF_8), ImmutableCbsClientConfiguration.class); - LOGGER.info("Evaluated variables: {}", config); - return Mono.just(config); - } catch (Exception e) { - return Mono.error(new RuntimeException("Failed to load/parse CBS client configuration file", e)); - } - } - -} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshScheduler.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshScheduler.java new file mode 100644 index 00000000..fc4b9dff --- /dev/null +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshScheduler.java @@ -0,0 +1,97 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.prh.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import reactor.core.Disposable; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Duration; + +@Component +public class CbsConfigRefreshScheduler { + + private static final Logger LOGGER = LoggerFactory.getLogger(CbsConfigRefreshScheduler.class); + private static final String CBS_UPDATES_INTERVAL_PROPERTY = "cbs.updates-interval"; + private static final Duration NO_UPDATES = Duration.ZERO; + + private final ContextRefresher contextRefresher; + private final Environment environment; + private final Scheduler scheduler; + private transient Disposable refreshEventsStreamHandle; + + + public CbsConfigRefreshScheduler(ContextRefresher contextRefresher, Environment environment) { + this.contextRefresher = contextRefresher; + this.environment = environment; + this.scheduler = Schedulers.newElastic("conf-updates"); + } + + @PostConstruct + public void startPollingForCbsUpdates() { + startPollingForCbsUpdates(getCbsUpdatesInterval()); + } + + private void startPollingForCbsUpdates(Duration updatesInterval) { + if (!updatesInterval.equals(NO_UPDATES)) { + LOGGER.info("Configuring pulling for CBS updates in every {}", updatesInterval); + refreshEventsStreamHandle = Flux.interval(updatesInterval, scheduler) + .doOnNext(i -> { + LOGGER.debug("Requesting context refresh"); + contextRefresher.refresh(); + }) + .onErrorContinue((e, o) -> LOGGER.error("Failed fetching config updates from CBS", e)) + .subscribe(); + } + } + + @EventListener + public void onEnvironmentChanged(EnvironmentChangeEvent event) { + if (event.getKeys().contains(CBS_UPDATES_INTERVAL_PROPERTY)) { + LOGGER.info("CBS config polling interval changed to {}", environment.getProperty(CBS_UPDATES_INTERVAL_PROPERTY)); + stopPollingForCbsUpdates(); + startPollingForCbsUpdates(getCbsUpdatesInterval()); + } + } + + private Duration getCbsUpdatesInterval() { + return environment.getProperty(CBS_UPDATES_INTERVAL_PROPERTY, Duration.class, NO_UPDATES); + } + + @PreDestroy + private void stopPollingForCbsUpdates() { + if(refreshEventsStreamHandle != null) { + LOGGER.debug("Stopping pulling for CBS updates"); + refreshEventsStreamHandle.dispose(); + } + } + +} diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfiguration.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfiguration.java index 1f752733..c1226359 100644 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfiguration.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfiguration.java @@ -22,24 +22,17 @@ package org.onap.dcaegen2.services.prh.configuration; import com.google.gson.JsonObject; import org.onap.dcaegen2.services.sdk.rest.services.aai.client.config.AaiClientConfiguration; -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory; -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests; -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; import org.onap.dcaegen2.services.sdk.rest.services.dmaap.client.api.DmaapClientFactory; import org.onap.dcaegen2.services.sdk.rest.services.dmaap.client.api.MessageRouterPublisher; import org.onap.dcaegen2.services.sdk.rest.services.dmaap.client.api.MessageRouterSubscriber; import org.onap.dcaegen2.services.sdk.rest.services.dmaap.client.model.MessageRouterPublishRequest; import org.onap.dcaegen2.services.sdk.rest.services.dmaap.client.model.MessageRouterSubscribeRequest; -import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Configuration; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; import java.util.Optional; -@Configuration + public class CbsConfiguration implements Config { private static final Logger LOGGER = LoggerFactory.getLogger(CbsConfiguration.class); private static final String CBS_CONFIG_MISSING = "CBS config missing"; @@ -50,26 +43,8 @@ public class CbsConfiguration implements Config { private MessageRouterSubscribeRequest messageRouterCBSSubscribeRequest; private MessageRouterPublishRequest messageRouterCBSUpdatePublishRequest; - private final CbsClientConfigurationResolver cbsClientConfigurationResolver; - - public CbsConfiguration(CbsClientConfigurationResolver cbsClientConfigurationResolver) { - this.cbsClientConfigurationResolver = cbsClientConfigurationResolver; - } - - public void runTask() { - Flux.defer(cbsClientConfigurationResolver::resolveCbsClientConfiguration) - .subscribeOn(Schedulers.parallel()) - .subscribe(this::parsingConfigSuccess, this::parsingConfigError); - } - private void parsingConfigSuccess(CbsClientConfiguration cbsClientConfiguration) { - LOGGER.debug("Fetching PRH configuration from Consul"); - CbsClientFactory.createCbsClient(cbsClientConfiguration) - .flatMap(cbsClient -> cbsClient.get(CbsRequests.getAll(RequestDiagnosticContext.create()))) - .subscribe(this::parseCBSConfig, this::cbsConfigError); - } - - private void parseCBSConfig(JsonObject jsonObject) { + public void parseCBSConfig(JsonObject jsonObject) { LOGGER.info("Received application configuration: {}", jsonObject); CbsContentParser consulConfigurationParser = new CbsContentParser(jsonObject); @@ -85,14 +60,6 @@ public class CbsConfiguration implements Config { messageRouterCBSSubscribeRequest = consulConfigurationParser.getMessageRouterSubscribeRequest(); } - private void parsingConfigError(Throwable throwable) { - LOGGER.warn("Failed to process system environments", throwable); - } - - private void cbsConfigError(Throwable throwable) { - LOGGER.warn("Failed to gather configuration from ConfigBindingService/Consul", throwable); - } - @Override public MessageRouterPublisher getMessageRouterPublisher() { diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/PrhAppConfig.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/PrhAppConfig.java index 71d707bc..31794f6b 100644 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/PrhAppConfig.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/PrhAppConfig.java @@ -23,26 +23,28 @@ package org.onap.dcaegen2.services.prh.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; -import javax.annotation.PostConstruct; import java.io.IOException; import java.nio.charset.Charset; /** * @author <a href="mailto:przemyslaw.wasala@nokia.com">Przemysław Wąsala</a> on 4/9/18 */ -@Configuration +@Component public class PrhAppConfig { private static final Logger LOGGER = LoggerFactory.getLogger(PrhAppConfig.class); @Value("classpath:git_info.json") private Resource gitInfo; - @PostConstruct - private void printGitInfo() throws IOException { + + @EventListener + public void onApplicationStartedEvent(ApplicationStartedEvent applicationStartedEvent) throws IOException { if(LOGGER.isDebugEnabled()) { LOGGER.debug("Git info={}", StreamUtils.copyToString(gitInfo.getInputStream(), Charset.defaultCharset())); } @@ -51,4 +53,5 @@ public class PrhAppConfig { public Resource getGitInfo() { return gitInfo; } + } diff --git a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/tasks/ScheduledTasksRunner.java b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/tasks/ScheduledTasksRunner.java index bc13ddc4..c3eaa12f 100644 --- a/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/tasks/ScheduledTasksRunner.java +++ b/prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/tasks/ScheduledTasksRunner.java @@ -21,18 +21,15 @@ package org.onap.dcaegen2.services.prh.tasks; import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledFuture; import javax.annotation.PostConstruct; -import org.onap.dcaegen2.services.prh.configuration.CbsConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.MarkerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; @@ -44,24 +41,17 @@ import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling public class ScheduledTasksRunner { - private static final int SCHEDULING_DELAY_FOR_PRH_TASKS = 10; - private static final int SCHEDULING_REQUEST_FOR_CONFIGURATION_DELAY = 5; private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTasksRunner.class); private static final Marker ENTRY = MarkerFactory.getMarker("ENTRY"); private static volatile List<ScheduledFuture> scheduledPrhTaskFutureList = new ArrayList<>(); private final TaskScheduler taskScheduler; private final ScheduledTasks scheduledTask; - private final CbsConfiguration cbsConfiguration; - @Autowired - public ScheduledTasksRunner(TaskScheduler taskScheduler, - ScheduledTasks scheduledTask, - CbsConfiguration cbsConfiguration) { + public ScheduledTasksRunner(TaskScheduler taskScheduler, ScheduledTasks scheduledTask) { this.taskScheduler = taskScheduler; this.scheduledTask = scheduledTask; - this.cbsConfiguration = cbsConfiguration; } /** @@ -82,9 +72,6 @@ public class ScheduledTasksRunner { LOGGER.info(ENTRY, "Start scheduling PRH workflow"); if (scheduledPrhTaskFutureList.isEmpty()) { scheduledPrhTaskFutureList.add(taskScheduler - .scheduleAtFixedRate(cbsConfiguration::runTask, Instant.now(), - Duration.ofMinutes(SCHEDULING_REQUEST_FOR_CONFIGURATION_DELAY))); - scheduledPrhTaskFutureList.add(taskScheduler .scheduleWithFixedDelay(scheduledTask::scheduleMainPrhEventTask, Duration.ofSeconds(SCHEDULING_DELAY_FOR_PRH_TASKS))); return true; diff --git a/prh-app-server/src/main/resources/META-INF/spring.factories b/prh-app-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..78fde6e2 --- /dev/null +++ b/prh-app-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.cloud.bootstrap.BootstrapConfiguration=org.onap.dcaegen2.services.bootstrap.CbsBootstrapConfiguration
\ No newline at end of file diff --git a/prh-app-server/src/main/resources/application.yaml b/prh-app-server/src/main/resources/application.yaml index 8139036c..2d244887 100644 --- a/prh-app-server/src/main/resources/application.yaml +++ b/prh-app-server/src/main/resources/application.yaml @@ -10,13 +10,16 @@ server: key-password: nokiapnf keyAlias: tomcat-localhost -management.endpoints.web.exposure.include: "loggers" - +management.endpoints.web.exposure.include: "loggers,refresh,env,health" --- spring: profiles: dev logging: level: - org.onap.dcaegen2.services.prh: info - org.onap.dcaegen2.services.sdk: info
\ No newline at end of file + org.onap.dcaegen2.services.prh: debug + org.onap.dcaegen2.services.sdk: debug + +management.endpoints.web.exposure.include: "*" + + diff --git a/prh-app-server/src/main/resources/bootstrap.yaml b/prh-app-server/src/main/resources/bootstrap.yaml new file mode 100644 index 00000000..176c6bec --- /dev/null +++ b/prh-app-server/src/main/resources/bootstrap.yaml @@ -0,0 +1,23 @@ +spring: + application: + name: dcae-prh + + cloud: + config: + enabled: false + + +logging: + level: + org.springframework.boot.SpringApplication: warn + org.springframework.context.support.PostProcessorRegistrationDelegate: warn + +cbs: + hostname: cbs + port: 10000 + app-name: dcae-prh + updates-interval: 5m + fetch-retries: + max-attempts: 10 + first-backoff: 3s + max-backoff: 15s
\ No newline at end of file diff --git a/prh-app-server/src/main/resources/cbs_client_config.json b/prh-app-server/src/main/resources/cbs_client_config.json deleted file mode 100644 index 0f196a1e..00000000 --- a/prh-app-server/src/main/resources/cbs_client_config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "hostname": "cbs", - "port": 10000, - "appName": "dcae-prh" -}
\ No newline at end of file diff --git a/prh-app-server/src/main/resources/logback-spring.xml b/prh-app-server/src/main/resources/logback-spring.xml index 03f4a103..060cf6c5 100644 --- a/prh-app-server/src/main/resources/logback-spring.xml +++ b/prh-app-server/src/main/resources/logback-spring.xml @@ -16,8 +16,6 @@ |%replace(%replace(%marker){'\t','\\\\t'}){'\n','\\\\n'} |%thread |%n"/> - <variable name="logLevel" value="${LOG_LEVEL:-ERROR}"/> - <variable name="logLevelPrh" value="${PRH_LOG_LEVEL:-WARN}"/> <springProfile name="prod"> <appender class="ch.qos.logback.core.ConsoleAppender" name="CONSOLE" target="SYSTEM_OUT"> @@ -39,10 +37,7 @@ </rollingPolicy> </appender> - <logger name="org.onap.dcaegen2.services.prh" level="${logLevelPrh}"/> - <logger name="org.onap.dcaegen2.services.sdk" level="${logLevelPrh}"/> - - <root level="${logLevel}"> + <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="ROLLING-FILE"/> </root> diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolverTest.java b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolverTest.java index 11981b50..2f871618 100644 --- a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolverTest.java +++ b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolverTest.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.dcaegen2.services.prh.configuration; +package org.onap.dcaegen2.services.bootstrap; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.*; @@ -26,27 +26,26 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; -import reactor.core.publisher.Mono; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CbsClientConfigurationResolverTest { @Mock - private CbsClientConfigFileReader cbsClientConfigFileReader; + private CbsProperties cbsProperties; @Mock - private CbsClientConfiguration configurationFromFile; + private CbsClientConfiguration defaultCbsClientConfigFromSpringProps; @Test - @DisabledIfEnvironmentVariable(named = "CONSUL_HOST", matches = ".+") + @DisabledIfEnvironmentVariable(named = "CONFIG_BINDING_SERVICE", matches = ".+") void whenCbsEnvPropertiesAreNotePresentInEnvironment_ShouldFallbackToLoadingDefaults() { - when(cbsClientConfigFileReader.readConfig()).thenReturn(Mono.just(configurationFromFile)); - CbsClientConfigurationResolver cbsClientConfigurationResolver = new CbsClientConfigurationResolver(cbsClientConfigFileReader); + when(cbsProperties.toCbsClientConfiguration()).thenReturn(defaultCbsClientConfigFromSpringProps); + CbsClientConfigurationResolver cbsClientConfigurationResolver = new CbsClientConfigurationResolver(cbsProperties); - CbsClientConfiguration config = cbsClientConfigurationResolver.resolveCbsClientConfiguration().block(); + CbsClientConfiguration config = cbsClientConfigurationResolver.resolveCbsClientConfiguration(); - assertSame(configurationFromFile, config); + assertSame(defaultCbsClientConfigFromSpringProps, config); } }
\ No newline at end of file diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverterTest.java b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverterTest.java new file mode 100644 index 00000000..da9a0008 --- /dev/null +++ b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverterTest.java @@ -0,0 +1,86 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CbsJsonToPropertyMapConverterTest { + + private static final JsonObject SOME_JSON_FROM_CBS = asJsonObject( + "{\n" + + " \"config\": {\n" + + " \"someStringProp\": \"foo\",\n" + + " \"someNumericalProp\": 123,\n" + + " \"someBooleanProp\": true,\n" + + " \"someObjectProp\": {\n" + + " \"bar\": \"baz\"\n" + + " }\n" + + " }\n" + + "}" + ); + + private CbsJsonToPropertyMapConverter cbsJsonToPropertyMapConverter = new CbsJsonToPropertyMapConverter(); + + @Test + void shouldExtractPrimitivePropertiesToSimpleJavaTypes() { + Map<String, Object> map = cbsJsonToPropertyMapConverter.convertToMap(SOME_JSON_FROM_CBS); + + assertThat(map).containsEntry("someStringProp", "foo"); + assertThat(map).containsEntry("someNumericalProp", 123L); + assertThat(map).containsEntry("someBooleanProp", true); + + } + + @Test + void shouldLeaveComplexPropertiesAsJsonTypes() { + Map<String, Object> map = cbsJsonToPropertyMapConverter.convertToMap(SOME_JSON_FROM_CBS); + + assertThat(map).hasEntrySatisfying("someObjectProp", hasClass(JsonObject.class)); + } + + @Test + void shouldProduceDescriptiveExceptionInCaseExpectedRootElementInCbsJsonIsMissing() { + assertThatThrownBy(() -> cbsJsonToPropertyMapConverter.convertToMap(asJsonObject("{}"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Missing expected 'config' property in json from CBS."); + } + + + private static JsonObject asJsonObject(String jsonString) { + return new JsonParser().parse(jsonString).getAsJsonObject(); + } + + private static Condition<Object> hasClass(Class clazz) { + return new Condition<Object>(clazz.getCanonicalName()){ + public boolean matches(Object value) { + return value.getClass().equals(clazz); + } + }; + } +}
\ No newline at end of file diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocatorTest.java b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocatorTest.java new file mode 100644 index 00000000..faf1867a --- /dev/null +++ b/prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocatorTest.java @@ -0,0 +1,164 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.bootstrap; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonObject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.onap.dcaegen2.services.prh.configuration.CbsConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.RequestPath; +import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import reactor.core.publisher.Mono; +import reactor.test.scheduler.VirtualTimeScheduler; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + + +@ExtendWith(MockitoExtension.class) +class CbsPropertySourceLocatorTest { + + private static final RequestPath GET_ALL_REQUEST_PATH = CbsRequests.getAll(RequestDiagnosticContext.create()).requestPath(); + + private CbsProperties cbsProperties = new CbsProperties(); + @Mock + private CbsJsonToPropertyMapConverter cbsJsonToPropertyMapConverter; + @Mock + private CbsClientConfiguration cbsClientConfiguration; + @Mock + private CbsClientConfigurationResolver cbsClientConfigurationResolver; + @Mock + private CbsClientFactoryFacade cbsClientFactoryFacade; + @Mock + private CbsConfiguration cbsConfiguration; + @Mock + private Environment environment; + @Mock + private CbsClient cbsClient; + @Mock + private JsonObject cbsConfigJsonObject; + private Map<String, Object> cbsConfigMap = ImmutableMap.of("foo", "bar"); + + private VirtualTimeScheduler virtualTimeScheduler; + + private CbsPropertySourceLocator cbsPropertySourceLocator; + + + @BeforeEach + void setup() { + virtualTimeScheduler = VirtualTimeScheduler.getOrSet(); + + when(cbsClientConfigurationResolver.resolveCbsClientConfiguration()).thenReturn(cbsClientConfiguration); + when(cbsClientFactoryFacade.createCbsClient(cbsClientConfiguration)).thenReturn(Mono.just(cbsClient)); + + cbsPropertySourceLocator = new CbsPropertySourceLocator( + cbsProperties, cbsJsonToPropertyMapConverter, cbsClientConfigurationResolver, + cbsClientFactoryFacade, cbsConfiguration); + } + + @AfterEach + void cleanup() { + virtualTimeScheduler.dispose(); + } + + + @Test + void shouldBuildCbsPropertySourceBasedOnDataFetchedUsingCbsClient() { + when(cbsClient.get(argThat(request -> request.requestPath().equals(GET_ALL_REQUEST_PATH)))) + .thenReturn(Mono.just(cbsConfigJsonObject)); + when(cbsJsonToPropertyMapConverter.convertToMap(cbsConfigJsonObject)).thenReturn(cbsConfigMap); + + PropertySource<?> propertySource = cbsPropertySourceLocator.locate(environment); + + assertThat(propertySource).extracting(PropertySource::getName).isEqualTo("cbs"); + assertThat(propertySource).extracting(s -> s.getProperty("foo")).isEqualTo("bar"); + } + + + @Test + void shouldUpdateCbsConfigurationStateBasedOnDataFetchedUsingCbsClient() { + when(cbsClient.get(argThat(request -> request.requestPath().equals(GET_ALL_REQUEST_PATH)))) + .thenReturn(Mono.just(cbsConfigJsonObject)); + when(cbsJsonToPropertyMapConverter.convertToMap(cbsConfigJsonObject)).thenReturn(cbsConfigMap); + + cbsPropertySourceLocator.locate(environment); + + verify(cbsConfiguration).parseCBSConfig(cbsConfigJsonObject); + } + + + @Test + void shouldPropagateExceptionWhenCbsConfigurationParsingFails() { + when(cbsClient.get(any(CbsRequest.class))).thenReturn(Mono.just(cbsConfigJsonObject)); + + RuntimeException someCbsConfigParsingException = new RuntimeException("boom!"); + doThrow(someCbsConfigParsingException).when(cbsConfiguration).parseCBSConfig(cbsConfigJsonObject); + + assertThatThrownBy(() -> cbsPropertySourceLocator.locate(environment)) + .isSameAs(someCbsConfigParsingException); + } + + @Test + void shouldRetryFetchingConfigFromCbsInCaseOfFailure() { + assumeThat(cbsProperties.getFetchRetries().getMaxAttempts()).isGreaterThan(1); + when(cbsClient.get(any(CbsRequest.class))) + .thenReturn(Mono.defer(() -> { + virtualTimeScheduler.advanceTimeBy(cbsProperties.getFetchRetries().getMaxBackoff()); + return Mono.error(new RuntimeException("some connection failure")); + })) + .thenReturn(Mono.just(cbsConfigJsonObject)); + when(cbsJsonToPropertyMapConverter.convertToMap(cbsConfigJsonObject)).thenReturn(cbsConfigMap); + + PropertySource<?> propertySource = cbsPropertySourceLocator.locate(environment); + + assertThat(propertySource).extracting(s -> s.getProperty("foo")).isEqualTo("bar"); + } + + @Test + void shouldFailAfterExhaustingAllOfConfiguredRetryAttempts() { + assumeThat(cbsProperties.getFetchRetries().getMaxAttempts()).isGreaterThan(1); + when(cbsClient.get(any(CbsRequest.class))) + .thenReturn(Mono.defer(() -> { + virtualTimeScheduler.advanceTimeBy(cbsProperties.getFetchRetries().getMaxBackoff()); + return Mono.error(new RuntimeException("some connection failure")); + })); + + assertThatThrownBy(() -> cbsPropertySourceLocator.locate(environment)) + .hasMessageContaining("Retries exhausted") + .hasMessageContaining(cbsProperties.getFetchRetries().getMaxAttempts().toString()); + } +} diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshSchedulerTest.java b/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshSchedulerTest.java new file mode 100644 index 00000000..7ea08aea --- /dev/null +++ b/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshSchedulerTest.java @@ -0,0 +1,138 @@ +/* + * ============LICENSE_START======================================================= + * PNF-REGISTRATION-HANDLER + * ================================================================================ + * Copyright (C) 2019 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.prh.configuration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.core.env.Environment; +import reactor.test.scheduler.VirtualTimeScheduler; + +import java.time.Duration; +import java.util.Collections; + +import static org.mockito.Mockito.*; + + +@ExtendWith(MockitoExtension.class) +class CbsConfigRefreshSchedulerTest { + + private static final Duration SOME_UPDATES_INTERVAL = Duration.ofMinutes(5); + private static final String CBS_UPDATES_INTERVAL_PROPERTY = "cbs.updates-interval"; + + @Mock + private ContextRefresher contextRefresher; + @Mock + private Environment environment; + + private VirtualTimeScheduler virtualTimeScheduler; + + private CbsConfigRefreshScheduler cbsConfigRefreshScheduler; + + + @BeforeEach + void setUp() { + virtualTimeScheduler = VirtualTimeScheduler.getOrSet(); + when(environment.getProperty(CBS_UPDATES_INTERVAL_PROPERTY, Duration.class, Duration.ZERO)) + .thenReturn(SOME_UPDATES_INTERVAL); + + cbsConfigRefreshScheduler = new CbsConfigRefreshScheduler(contextRefresher, environment); + } + + @AfterEach + void tearDown() { + virtualTimeScheduler.dispose(); + } + + @Test + void configRefreshUpdatesShouldBeFiredAccordingToConfiguredInterval() { + cbsConfigRefreshScheduler.startPollingForCbsUpdates(); + + verify(contextRefresher, times(0)).refresh(); + + virtualTimeScheduler.advanceTimeBy(SOME_UPDATES_INTERVAL); + verify(contextRefresher, times(1)).refresh(); + + virtualTimeScheduler.advanceTimeBy(SOME_UPDATES_INTERVAL); + verify(contextRefresher, times(2)).refresh(); + } + + @Test + void whenConfigUpdateIntervalIsSetToZero_UpdatesShouldNotBeExecuted() { + when(environment.getProperty(CBS_UPDATES_INTERVAL_PROPERTY, Duration.class, Duration.ZERO)) + .thenReturn(Duration.ZERO); + + cbsConfigRefreshScheduler.startPollingForCbsUpdates(); + + virtualTimeScheduler.advanceTimeBy(Duration.ofHours(10)); + + verifyZeroInteractions(contextRefresher); + } + + @Test + void whenUpdateFails_shouldContinueWithUpdateRequestsAccordingToConfiguredSchedule() { + when(contextRefresher.refresh()) + .thenThrow(new RuntimeException("kaboom!")) + .thenReturn(Collections.emptySet()); + + cbsConfigRefreshScheduler.startPollingForCbsUpdates(); + + virtualTimeScheduler.advanceTimeBy(SOME_UPDATES_INTERVAL.plus(SOME_UPDATES_INTERVAL)); + verify(contextRefresher, times(2)).refresh(); + } + + + @Test + void whenUpdatesIntervalIsChangedInEnvironment_UpdatesShouldBeRescheduled() { + when(environment.getProperty(CBS_UPDATES_INTERVAL_PROPERTY, Duration.class, Duration.ZERO)) + .thenReturn(Duration.ofMinutes(30)) + .thenReturn(Duration.ofSeconds(10)); + + cbsConfigRefreshScheduler.startPollingForCbsUpdates(); + + cbsConfigRefreshScheduler.onEnvironmentChanged( + new EnvironmentChangeEvent(Collections.singleton(CBS_UPDATES_INTERVAL_PROPERTY))); + + virtualTimeScheduler.advanceTimeBy(Duration.ofMinutes(1)); + + verify(contextRefresher, times(6)).refresh(); + } + + + @Test + void whenEnvironmentChangeDoesNotAffectUpdatesInterval_UpdatesScheduleShouldNotBeImpacted() { + cbsConfigRefreshScheduler.startPollingForCbsUpdates(); + + Duration envChangeDelay = Duration.ofMinutes(1); + virtualTimeScheduler.advanceTimeBy(envChangeDelay); + + cbsConfigRefreshScheduler.onEnvironmentChanged(new EnvironmentChangeEvent(Collections.emptySet())); + + virtualTimeScheduler.advanceTimeBy(SOME_UPDATES_INTERVAL.minus(envChangeDelay)); + + verify(contextRefresher).refresh(); + } +}
\ No newline at end of file diff --git a/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/tasks/CbsConfigTestConfig.java b/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/tasks/CbsConfigTestConfig.java new file mode 100644 index 00000000..9e3ef2e1 --- /dev/null +++ b/prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/tasks/CbsConfigTestConfig.java @@ -0,0 +1,34 @@ +/* + * ============LICENSE_START======================================================= + * PROJECT + * ================================================================================ + * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.services.prh.tasks; + +import org.onap.dcaegen2.services.prh.configuration.CbsConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CbsConfigTestConfig { + + @Bean + public CbsConfiguration cbsConfiguration() { + return new CbsConfiguration(); + } +} diff --git a/prh-app-server/src/test/resources/bootstrap.yaml b/prh-app-server/src/test/resources/bootstrap.yaml new file mode 100644 index 00000000..bbb2dcf9 --- /dev/null +++ b/prh-app-server/src/test/resources/bootstrap.yaml @@ -0,0 +1,2 @@ +cbs: + enabled: false
\ No newline at end of file |