Create a json schema for validation of the configuration of the A1 PMS - A1 Jakarta 84/124384/7
authorPatrikBuhr <patrik.buhr@est.tech>
Tue, 21 Sep 2021 12:47:59 +0000 (14:47 +0200)
committerPatrikBuhr <patrik.buhr@est.tech>
Wed, 22 Sep 2021 16:06:39 +0000 (18:06 +0200)
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

13 files changed:
a1-policy-management/config/application.yaml
a1-policy-management/config/application_configuration.json
a1-policy-management/pom.xml
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfigParser.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ConfigurationController.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/tasks/RefreshConfigTask.java
a1-policy-management/src/main/resources/application_configuration_schema.json [new file with mode: 0644]
a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfigParserTest.java
a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ConfigurationControllerTest.java
a1-policy-management/src/test/resources/test_application_configuration.json
a1-policy-management/src/test/resources/test_application_configuration_with_dmaap_config.json
csit/scripts/healthcheck/config/application_configuration.json.nosdnc

index a40f172..e1a778c 100644 (file)
@@ -74,4 +74,5 @@ app:
     http.proxy-type: HTTP
   # path where the service can store data
   vardata-directory: /var/policy-management-service
+  config-file-schema-path:
 
index 6c21b16..6187c86 100644 (file)
@@ -1,6 +1,6 @@
 {
-   "config": {
-      "//description": "Application configuration",
+   "description": "Application configuration",
+   "config": {  
       "controller": [
          {
             "name": "controller1",
@@ -21,4 +21,4 @@
          }
       ]
    }
-}
\ No newline at end of file
+}
index 4e3a9bd..221cc08 100644 (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
index 3a24519..40988e4 100644 (file)
@@ -44,6 +44,10 @@ public class ApplicationConfig {
     @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;
index a6af202..3cab8aa 100644 (file)
@@ -24,6 +24,10 @@ import com.google.gson.JsonArray;
 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;
@@ -38,6 +42,7 @@ import javax.validation.constraints.NotNull;
 
 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;
@@ -50,6 +55,11 @@ public class ApplicationConfigParser {
 
     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
@@ -66,6 +76,8 @@ public class ApplicationConfigParser {
 
     public ConfigParserResult parse(JsonObject root) throws ServiceException {
 
+        validateJsonObjectAgainstSchema(root);
+
         String dmaapProducerTopicUrl = "";
         String dmaapConsumerTopicUrl = "";
 
@@ -97,6 +109,35 @@ public class ApplicationConfigParser {
                 .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<>();
index b677a40..e07ea28 100644 (file)
@@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 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;
@@ -57,11 +58,17 @@ public class ConfigurationController {
     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(); //
@@ -85,7 +92,7 @@ public class ConfigurationController {
             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.");
index 3027981..6177ee1 100644 (file)
@@ -189,7 +189,7 @@ public class RefreshConfigTask {
 
     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();
diff --git a/a1-policy-management/src/main/resources/application_configuration_schema.json b/a1-policy-management/src/main/resources/application_configuration_schema.json
new file mode 100644 (file)
index 0000000..05135e7
--- /dev/null
@@ -0,0 +1,151 @@
+{
+  "$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
index 5e21858..d12fd6b 100644 (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;
@@ -44,12 +47,16 @@ import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException
 
 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();
@@ -153,6 +160,19 @@ class ApplicationConfigParserTest {
         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)
index adb29a7..d280555 100644 (file)
@@ -60,7 +60,9 @@ import reactor.test.StepVerifier;
 @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;
@@ -135,8 +137,7 @@ class ConfigurationControllerTest {
         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) {
index 3cbc371..959f53f 100644 (file)
@@ -1,6 +1,6 @@
-{
+{  
    "config": {
-      "//description": "Application configuration",
+      "description": "Application configuration",
       "ric": [
          {
             "name": "ric1",
@@ -36,4 +36,4 @@
          }
       }
    }
-}
\ No newline at end of file
+}
index deb88a0..800a54e 100644 (file)
@@ -1,6 +1,6 @@
 {
-    "config":{
-       "//description":"Application configuration",
+    "description":"Application configuration",
+    "config":{     
        "ric":[
           {
              "name":"ric1",
@@ -36,4 +36,4 @@
           }
        }
     }
- }
\ No newline at end of file
+ }