To create a json schema to be used for validation of the PMS configuration.
The schema shall be used by PMS.
It can also be used by the end user. The documentation should be updated on how can be done.
Issue-ID: CCSDK-3468
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
Change-Id: I9932fa42ff40681098764c8dc84ac201bb3fabaf
http.proxy-type: HTTP
# path where the service can store data
vardata-directory: /var/policy-management-service
+ config-file-schema-path:
{
- "config": {
- "//description": "Application configuration",
+ "description": "Application configuration",
+ "config": {
"controller": [
{
"name": "controller1",
}
]
}
-}
\ No newline at end of file
+}
<version>${commons-io.version}</version>
<scope>test</scope>
</dependency>
+ <!-- https://mvnrepository.com/artifact/com.github.erosb/everit-json-schema -->
+ <dependency>
+ <groupId>com.github.erosb</groupId>
+ <artifactId>everit-json-schema</artifactId>
+ <version>1.13.0</version>
+ </dependency>
</dependencies>
<build>
<plugins>
</plugin>
</plugins>
</build>
-</project>
+</project>
\ No newline at end of file
@Value("${app.filepath}")
private String localConfigurationFilePath;
+ @Getter
+ @Value("${app.config-file-schema-path:\"\"}")
+ private String configurationFileSchemaPath;
+
@Getter
@Value("${app.vardata-directory:null}")
private String vardataDirectory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.immutables.gson.Gson;
import org.immutables.value.Value;
+import org.json.JSONObject;
import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final String CONFIG = "config";
private static final String CONTROLLER = "controller";
+ private final ApplicationConfig applicationConfig;
+
+ public ApplicationConfigParser(ApplicationConfig applicationConfig) {
+ this.applicationConfig = applicationConfig;
+ }
@Value.Immutable
@Gson.TypeAdapters
public ConfigParserResult parse(JsonObject root) throws ServiceException {
+ validateJsonObjectAgainstSchema(root);
+
String dmaapProducerTopicUrl = "";
String dmaapConsumerTopicUrl = "";
.build();
}
+ private void validateJsonObjectAgainstSchema(Object object) throws ServiceException {
+ if (applicationConfig.getConfigurationFileSchemaPath() == null
+ || applicationConfig.getConfigurationFileSchemaPath().isEmpty()) {
+ return;
+ }
+
+ try {
+ String schemaAsString = readSchemaFile();
+
+ JSONObject schemaJSON = new JSONObject(schemaAsString);
+ var schema = org.everit.json.schema.loader.SchemaLoader.load(schemaJSON);
+
+ String objectAsString = object.toString();
+ JSONObject json = new JSONObject(objectAsString);
+ schema.validate(json);
+ } catch (Exception e) {
+ throw new ServiceException("Json schema validation failure: " + e.toString());
+ }
+ }
+
+ private String readSchemaFile() throws IOException {
+ ClassLoader classLoader = getClass().getClassLoader();
+ String filePath = applicationConfig.getConfigurationFileSchemaPath();
+ URL url = classLoader.getResource(filePath);
+ File file = new File(url.getFile());
+ return new String(Files.readAllBytes(file.toPath()));
+
+ }
+
private void checkConfigurationConsistency(List<RicConfig> ricConfigs,
Map<String, ControllerConfig> controllerConfigs) throws ServiceException {
Set<String> ricUrls = new HashSet<>();
import java.io.IOException;
import java.util.Optional;
+import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationConfig;
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationConfigParser;
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ConfigurationFile;
import org.onap.ccsdk.oran.a1policymanagementservice.controllers.VoidResponse;
public static final String API_NAME = "Management of configuration";
public static final String API_DESCRIPTION = "";
- @Autowired
- ConfigurationFile configurationFile;
+ private final ConfigurationFile configurationFile;
+ private final RefreshConfigTask refreshConfigTask;
+ private final ApplicationConfig applicationConfig;
- @Autowired
- RefreshConfigTask refreshConfigTask;
+ ConfigurationController(@Autowired ConfigurationFile configurationFile,
+ @Autowired RefreshConfigTask refreshConfigTask, @Autowired ApplicationConfig applicationConfig) {
+ this.configurationFile = configurationFile;
+ this.refreshConfigTask = refreshConfigTask;
+ this.applicationConfig = applicationConfig;
+
+ }
private static Gson gson = new GsonBuilder() //
.create(); //
validateConfigFileIsUsed();
String configAsString = gson.toJson(configuration);
JsonObject configJson = JsonParser.parseString(configAsString).getAsJsonObject();
- ApplicationConfigParser configParser = new ApplicationConfigParser();
+ ApplicationConfigParser configParser = new ApplicationConfigParser(applicationConfig);
configParser.parse(configJson);
configurationFile.writeFile(configJson);
logger.info("Configuration changed through REST call.");
private Mono<ApplicationConfigParser.ConfigParserResult> parseConfiguration(JsonObject jsonObject) {
try {
- ApplicationConfigParser parser = new ApplicationConfigParser();
+ ApplicationConfigParser parser = new ApplicationConfigParser(this.appConfig);
return Mono.just(parser.parse(jsonObject));
} catch (Exception e) {
String str = e.toString();
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "config": {
+ "type": "object",
+ "properties": {
+ "//description": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "controller": {
+ "type": "array",
+ "items": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "baseUrl": {
+ "type": "string"
+ },
+ "userName": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "baseUrl",
+ "userName",
+ "password"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "ric": {
+ "type": "array",
+ "items": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "baseUrl": {
+ "type": "string"
+ },
+ "controller": {
+ "type": "string"
+ },
+ "managedElementIds": {
+ "type": "array",
+ "items": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": [
+ "name",
+ "baseUrl",
+ "managedElementIds"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "streams_publishes": {
+ "type": "object",
+ "properties": {
+ "dmaap_publisher": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "dmaap_info": {
+ "type": "object",
+ "properties": {
+ "topic_url": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "topic_url"
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "dmaap_info"
+ ]
+ }
+ },
+ "required": [
+ "dmaap_publisher"
+ ]
+ },
+ "streams_subscribes": {
+ "type": "object",
+ "properties": {
+ "dmaap_subscriber": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "dmaap_info": {
+ "type": "object",
+ "properties": {
+ "topic_url": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "topic_url"
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "dmaap_info"
+ ]
+ }
+ },
+ "required": [
+ "dmaap_subscriber"
+ ]
+ }
+ },
+ "required": [
+ "ric"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "config"
+ ]
+}
\ No newline at end of file
package org.onap.ccsdk.oran.a1policymanagementservice.configuration;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
class ApplicationConfigParserTest {
- ApplicationConfigParser parserUnderTest = new ApplicationConfigParser();
+ ApplicationConfig applicationConfigMock = spy(new ApplicationConfig());
+ ApplicationConfigParser parserUnderTest = new ApplicationConfigParser(applicationConfigMock);
@Test
void whenCorrectConfig() throws Exception {
JsonObject jsonRootObject = getJsonRootObject();
+ when(applicationConfigMock.getConfigurationFileSchemaPath())
+ .thenReturn("application_configuration_schema.json");
+
ApplicationConfigParser.ConfigParserResult result = parserUnderTest.parse(jsonRootObject);
String topicUrl = result.dmaapProducerTopicUrl();
assertEquals(message, actualException.getMessage(), "Wrong error message when wrong member name in object");
}
+ @Test
+ void schemaValidationError() throws Exception {
+ when(applicationConfigMock.getConfigurationFileSchemaPath())
+ .thenReturn("application_configuration_schema.json");
+ JsonObject jsonRootObject = getJsonRootObject();
+ JsonObject json = jsonRootObject.getAsJsonObject("config");
+ json.remove("ric");
+
+ Exception actualException = assertThrows(ServiceException.class, () -> parserUnderTest.parse(jsonRootObject));
+
+ assertThat(actualException.getMessage()).contains("Json schema validation failure");
+ }
+
JsonObject getDmaapInfo(JsonObject jsonRootObject, String streamsPublishesOrSubscribes,
String dmaapPublisherOrSubscriber) throws Exception {
return jsonRootObject.getAsJsonObject("config").getAsJsonObject(streamsPublishesOrSubscribes)
@TestPropertySource(properties = { //
"server.ssl.key-store=./config/keystore.jks", //
"app.webclient.trust-store=./config/truststore.jks", //
- "app.vardata-directory=./target"})
+ "app.vardata-directory=./target", //
+ "app.config-file-schema-path=application_configuration_schema.json" //
+})
class ConfigurationControllerTest {
@Autowired
ApplicationContext context;
String url = "a1-policy/v2/configuration";
// Valid JSON but invalid configuration.
- testErrorCode(restClient().put(url, "{\"error\":\"error\"}"), HttpStatus.BAD_REQUEST,
- "Missing root configuration");
+ testErrorCode(restClient().put(url, "{\"error\":\"error\"}"), HttpStatus.BAD_REQUEST, "");
}
private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
-{
+{
"config": {
- "//description": "Application configuration",
+ "description": "Application configuration",
"ric": [
{
"name": "ric1",
}
}
}
-}
\ No newline at end of file
+}
{
"config": {
+ "//description" : "Test",
"controller": [
{
"name": "controller1",
{
- "config":{
- "//description":"Application configuration",
+ "description":"Application configuration",
+ "config":{
"ric":[
{
"name":"ric1",
}
}
}
- }
\ No newline at end of file
+ }