fetching configuration from CBS at application bootstrap 91/89791/4
authorgrabinsk <maciej.grabinski@nokia.com>
Tue, 4 Jun 2019 13:18:44 +0000 (15:18 +0200)
committergrabinsk <maciej.grabinski@nokia.com>
Fri, 14 Jun 2019 10:34:51 +0000 (12:34 +0200)
Change-Id: Ibaa56c38b87fc8b8cfa858f74b7bc201bd507b37
Issue-ID: DCAEGEN2-1544
Signed-off-by: grabinsk <maciej.grabinski@nokia.com>
26 files changed:
pom.xml
prh-app-server/pom.xml
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsBootstrapConfiguration.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolver.java [moved from prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolver.java with 62% similarity]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsClientFactoryFacade.java [moved from prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReaderTest.java with 51% similarity]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverter.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsProperties.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocator.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/bootstrap/RetryProperties.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/MainApp.java
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigFileReader.java [deleted file]
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshScheduler.java [new file with mode: 0644]
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/CbsConfiguration.java
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/configuration/PrhAppConfig.java
prh-app-server/src/main/java/org/onap/dcaegen2/services/prh/tasks/ScheduledTasksRunner.java
prh-app-server/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
prh-app-server/src/main/resources/application.yaml
prh-app-server/src/main/resources/bootstrap.yaml [new file with mode: 0644]
prh-app-server/src/main/resources/cbs_client_config.json [deleted file]
prh-app-server/src/main/resources/logback-spring.xml
prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsClientConfigurationResolverTest.java [moved from prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsClientConfigurationResolverTest.java with 73% similarity]
prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsJsonToPropertyMapConverterTest.java [new file with mode: 0644]
prh-app-server/src/test/java/org/onap/dcaegen2/services/bootstrap/CbsPropertySourceLocatorTest.java [new file with mode: 0644]
prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/configuration/CbsConfigRefreshSchedulerTest.java [new file with mode: 0644]
prh-app-server/src/test/java/org/onap/dcaegen2/services/prh/tasks/CbsConfigTestConfig.java [new file with mode: 0644]
prh-app-server/src/test/resources/bootstrap.yaml [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index ac1241e..9179e55 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -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>
         <version>${sdk.version}</version>
       </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>
index 50ee804..8bda3f0 100644 (file)
       <artifactId>common-dependency</artifactId>
     </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>
       <artifactId>tomcat-embed-websocket</artifactId>
     </dependency>
 
+    <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>
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 (file)
index 0000000..0297a67
--- /dev/null
@@ -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();
+    }
+}
  * ============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();
     }
 
 }
@@ -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.
  * ============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 (file)
index 0000000..bf4077b
--- /dev/null
@@ -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 (file)
index 0000000..18d4021
--- /dev/null
@@ -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 (file)
index 0000000..7b66020
--- /dev/null
@@ -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 (file)
index 0000000..44108a7
--- /dev/null
@@ -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;
+    }
+}
index 3445c07..84d9fcd 100644 (file)
@@ -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 (file)
index f481f4c..0000000
+++ /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 (file)
index 0000000..fc4b9df
--- /dev/null
@@ -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();
+        }
+    }
+
+}
index 1f75273..c122635 100644 (file)
@@ -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() {
index 71d707b..31794f6 100644 (file)
@@ -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;
     }
+
 }
index bc13ddc..c3eaa12 100644 (file)
 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;
     }
 
     /**
@@ -81,9 +71,6 @@ public class ScheduledTasksRunner {
     public synchronized boolean tryToStartTask() {
         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)));
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 (file)
index 0000000..78fde6e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.cloud.bootstrap.BootstrapConfiguration=org.onap.dcaegen2.services.bootstrap.CbsBootstrapConfiguration
\ No newline at end of file
index 8139036..2d24488 100644 (file)
@@ -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 (file)
index 0000000..176c6be
--- /dev/null
@@ -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 (file)
index 0f196a1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "hostname": "cbs",
-  "port": 10000,
-  "appName": "dcae-prh"
-}
\ No newline at end of file
index 03f4a10..060cf6c 100644 (file)
@@ -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">
       </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>
@@ -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 (file)
index 0000000..da9a000
--- /dev/null
@@ -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 (file)
index 0000000..faf1867
--- /dev/null
@@ -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 (file)
index 0000000..7ea08ae
--- /dev/null
@@ -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 (file)
index 0000000..9e3ef2e
--- /dev/null
@@ -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 (file)
index 0000000..bbb2dcf
--- /dev/null
@@ -0,0 +1,2 @@
+cbs:
+  enabled: false
\ No newline at end of file