Merge "Decouple configuration from application"
authorToine Siebelink <toine.siebelink@est.tech>
Thu, 4 Feb 2021 11:39:17 +0000 (11:39 +0000)
committerGerrit Code Review <gerrit@onap.org>
Thu, 4 Feb 2021 11:39:17 +0000 (11:39 +0000)
16 files changed:
cps-rest/docs/api/swagger/cpsAdmin.yml [changed mode: 0644->0755]
cps-rest/docs/api/swagger/cpsData.yml
cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java [changed mode: 0644->0755]
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy [changed mode: 0644->0755]
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy [new file with mode: 0644]
cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
cps-service/src/test/resources/e2e/basic/Data.txt [new file with mode: 0644]
cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang [new file with mode: 0644]

old mode 100644 (file)
new mode 100755 (executable)
index d33c8e5..18ed1a2
@@ -146,7 +146,7 @@ anchorByDataspaceAndAnchorName:
   get:
     tags:
       - cps-admin
-    summary: Read an anchor given a anchor and a dataspace - DRAFT
+    summary: Read an anchor given a anchor and a dataspace
     operationId: getAnchor
     parameters:
       - $ref: 'components.yaml#/components/parameters/dataspaceNameInPath'
index c33cf16..dcdb99a 100644 (file)
@@ -20,46 +20,47 @@ nodesByDataspaceAndAnchor:
         $ref: 'components.yaml#/components/responses/NotFound'
     x-codegen-request-body-name: xpath
 
-nodesByDataspace:
-  get:
+  post:
     tags:
       - cps-data
-    summary: Get all nodes for a given dataspace using an xpath or schema node identifier - DRAFT
-    operationId: getNode
+    summary: Create a node for a given anchor for the given dataspace
+    operationId: createNode
     parameters:
       - $ref: 'components.yaml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yaml#/components/parameters/anchorNameInPath'
+    requestBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            type: string
     responses:
-      200:
-        $ref: 'components.yaml#/components/responses/Ok'
+      201:
+        $ref: 'components.yaml#/components/responses/Created'
       400:
         $ref: 'components.yaml#/components/responses/BadRequest'
       401:
         $ref: 'components.yaml#/components/responses/Unauthorized'
       403:
         $ref: 'components.yaml#/components/responses/Forbidden'
-      404:
-        $ref: 'components.yaml#/components/responses/NotFound'
-    x-codegen-request-body-name: requestBody
 
-  post:
+nodesByDataspace:
+  get:
     tags:
       - cps-data
-    summary: Create a node for a given anchor for the given dataspace - DRAFT
-    operationId: createNode
+    summary: Get all nodes for a given dataspace using an xpath or schema node identifier - DRAFT
+    operationId: getNode
     parameters:
       - $ref: 'components.yaml#/components/parameters/dataspaceNameInPath'
-    requestBody:
-      content:
-        multipart/form-data:
-          schema:
-            $ref: 'components.yaml#/components/schemas/MultipartFile'
-      required: true
     responses:
-      201:
-        $ref: 'components.yaml#/components/responses/Created'
+      200:
+        $ref: 'components.yaml#/components/responses/Ok'
       400:
         $ref: 'components.yaml#/components/responses/BadRequest'
       401:
         $ref: 'components.yaml#/components/responses/Unauthorized'
       403:
-        $ref: 'components.yaml#/components/responses/Forbidden'
\ No newline at end of file
+        $ref: 'components.yaml#/components/responses/Forbidden'
+      404:
+        $ref: 'components.yaml#/components/responses/NotFound'
+    x-codegen-request-body-name: requestBody
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index 8f4bdb7..d74e9b1
@@ -102,7 +102,8 @@ public class AdminRestController implements CpsAdminApi {
 
     @Override
     public ResponseEntity<Object> getAnchor(final String dataspaceName, final String anchorName) {
-        return null;
+        final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+        return new ResponseEntity<>(anchor, HttpStatus.OK);
     }
 
     @Override
index 61f9399..9b31df5 100644 (file)
 package org.onap.cps.rest.controller;
 
 import javax.validation.Valid;
-import org.modelmapper.ModelMapper;
-import org.onap.cps.api.CpsAdminService;
+import javax.validation.constraints.NotNull;
+import org.onap.cps.api.CpsDataService;
 import org.onap.cps.rest.api.CpsDataApi;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
 
 @RestController
 @RequestMapping("${rest.api.cps-base-path}")
 public class DataRestController implements CpsDataApi {
 
     @Autowired
-    private CpsAdminService cpsAdminService;
-
-    @Autowired
-    private ModelMapper modelMapper;
+    private CpsDataService cpsDataService;
 
     @Override
-    public ResponseEntity<String> createNode(@Valid final MultipartFile multipartFile, final String dataspaceName) {
-        return null;
+    public ResponseEntity<String> createNode(@Valid final String jsonData, @NotNull final String dataspaceName,
+        @NotNull @Valid final String anchorName) {
+        cpsDataService.saveData(dataspaceName, anchorName, jsonData);
+        return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
     @Override
@@ -53,4 +52,5 @@ public class DataRestController implements CpsDataApi {
     public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName, final String anchorName) {
         return null;
     }
+
 }
old mode 100644 (file)
new mode 100755 (executable)
index 88adf10..926021e
 
 package org.onap.cps.rest.controller
 
+import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
 import org.modelmapper.ModelMapper
 import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.spi.exceptions.DataspaceAlreadyDefinedException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
@@ -40,12 +47,6 @@ import org.springframework.util.MultiValueMap
 import spock.lang.Specification
 import spock.lang.Unroll
 
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-
 @WebMvcTest
 class AdminRestControllerSpec extends Specification {
 
@@ -55,6 +56,9 @@ class AdminRestControllerSpec extends Specification {
     @SpringBean
     CpsAdminService mockCpsAdminService = Mock()
 
+    @SpringBean
+    CpsDataService mockCpsDataService = Mock()
+
     @SpringBean
     ModelMapper modelMapper = Mock()
 
@@ -64,29 +68,33 @@ class AdminRestControllerSpec extends Specification {
     @Value('${rest.api.cps-base-path}')
     def basePath
 
-    def anchorsEndpoint = '/v1/dataspaces/my_dataspace/anchors'
-    def schemaSetsEndpoint = '/v1/dataspaces/test-dataspace/schema-sets'
-    def schemaSetEndpoint = schemaSetsEndpoint + '/my_schema_set'
-
+    def dataspaceName = 'my_dataspace'
     def anchor = new Anchor(name: 'my_anchor')
     def anchorList = [anchor]
+    def anchorName = 'my_anchor'
+    def schemaSetName = 'my_schema_set'
 
-    def 'Create new dataspace'() {
-        when:
-            def response = performCreateDataspaceRequest("new-dataspace")
-        then: 'Service method is invoked with expected parameters'
-            1 * mockCpsAdminService.createDataspace("new-dataspace")
-        and: 'Dataspace is create successfully'
+    def 'Create new dataspace.'() {
+        given: 'an endpoint'
+            def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+        when: 'post is invoked'
+            def response = mvc.perform(
+                    post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
+        then: 'service method is invoked with expected parameters'
+            1 * mockCpsAdminService.createDataspace(dataspaceName)
+        and: 'dataspace is create successfully'
             response.status == HttpStatus.CREATED.value()
     }
 
-    def 'Create dataspace over existing with same name'() {
-        given:
+    def 'Create dataspace over existing with same name.'() {
+        given: 'an endpoint'
+            def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+        and: 'the service method throws an exception indicating the dataspace is already defined'
             def thrownException = new DataspaceAlreadyDefinedException("", new RuntimeException())
-            mockCpsAdminService.createDataspace("existing-dataspace") >> { throw thrownException }
-        when:
-            def response = performCreateDataspaceRequest("existing-dataspace")
-        then: 'Dataspace creation fails'
+            mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException }
+        when: 'post is invoked'
+            def response = mvc.perform(post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
+        then: 'dataspace creation fails'
             response.status == HttpStatus.BAD_REQUEST.value()
     }
 
@@ -94,10 +102,13 @@ class AdminRestControllerSpec extends Specification {
         def yangResourceMapCapture
         given: 'single yang file'
             def multipartFile = createMultipartFile("filename.yang", "content")
+        and: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
         when: 'file uploaded with schema set create request'
-            def response = performCreateSchemaSetRequest(multipartFile)
+            def response = mvc.perform(multipart(schemaSetEndpoint)
+                    .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
         then: 'associated service method is invoked with expected parameters'
-            1 * mockCpsModuleService.createSchemaSet('test-dataspace', 'test-schema-set', _) >>
+            1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
                     { args -> yangResourceMapCapture = args[2] }
             yangResourceMapCapture['filename.yang'] == 'content'
         and: 'response code indicates success'
@@ -108,10 +119,13 @@ class AdminRestControllerSpec extends Specification {
         def yangResourceMapCapture
         given: 'zip archive with multiple .yang files inside'
             def multipartFile = createZipMultipartFileFromResource("/yang-files-set.zip")
+        and: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
         when: 'file uploaded with schema set create request'
-            def response = performCreateSchemaSetRequest(multipartFile)
+            def response = mvc.perform(multipart(schemaSetEndpoint)
+                    .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
         then: 'associated service method is invoked with expected parameters'
-            1 * mockCpsModuleService.createSchemaSet('test-dataspace', 'test-schema-set', _) >>
+            1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
                     { args -> yangResourceMapCapture = args[2] }
             yangResourceMapCapture['assembly.yang'] == "fake assembly content 1\n"
             yangResourceMapCapture['component.yang'] == "fake component content 1\n"
@@ -121,8 +135,11 @@ class AdminRestControllerSpec extends Specification {
 
     @Unroll
     def 'Create schema set from zip archive having #caseDescriptor.'() {
+        given: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
         when: 'zip archive having #caseDescriptor is uploaded with create schema set request'
-            def response = performCreateSchemaSetRequest(multipartFile)
+            def response = mvc.perform(multipart(schemaSetEndpoint)
+                    .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
         then: 'create schema set rejected'
             response.status == HttpStatus.BAD_REQUEST.value()
         where: 'following cases are tested'
@@ -134,16 +151,23 @@ class AdminRestControllerSpec extends Specification {
     def 'Create schema set from file with unsupported filename extension.'() {
         given: 'file with unsupported filename extension (.doc)'
             def multipartFile = createMultipartFile("filename.doc", "content")
+        and: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
         when: 'file uploaded with schema set create request'
-            def response = performCreateSchemaSetRequest(multipartFile)
+            def response = mvc.perform(multipart(schemaSetEndpoint)
+                    .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
         then: 'create schema set rejected'
             response.status == HttpStatus.BAD_REQUEST.value()
     }
 
     @Unroll
     def 'Create schema set from #fileType file with IOException occurrence on processing.'() {
+        given: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
         when: 'file uploaded with schema set create request'
-            def response = performCreateSchemaSetRequest(createMultipartFileForIOException(fileType))
+            def multipartFile = createMultipartFileForIOException(fileType)
+            def response = mvc.perform(multipart(schemaSetEndpoint)
+                    .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
         then: 'the error response returned indicating internal server error occurrence'
             response.status == HttpStatus.INTERNAL_SERVER_ERROR.value()
         where: 'following file types are used'
@@ -151,29 +175,84 @@ class AdminRestControllerSpec extends Specification {
     }
 
     def 'Delete schema set.'() {
+        given: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
         when: 'delete schema set endpoint is invoked'
-            def response = performDeleteRequest(schemaSetEndpoint)
+            def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
         then: 'associated service method is invoked with expected parameters'
-            1 * mockCpsModuleService.deleteSchemaSet('test-dataspace', 'my_schema_set', CASCADE_DELETE_PROHIBITED)
+            1 * mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED)
         and: 'response code indicates success'
             response.status == HttpStatus.NO_CONTENT.value()
     }
 
     def 'Delete schema set which is in use.'() {
-        given: 'the service method throws an exception indicating the schema set is in use'
-            def thrownException = new SchemaSetInUseException('test-dataspace', 'my_schema_set')
-            mockCpsModuleService.deleteSchemaSet('test-dataspace', 'my_schema_set', CASCADE_DELETE_PROHIBITED) >>
+        given: 'service method throws an exception indicating the schema set is in use'
+            def thrownException = new SchemaSetInUseException(dataspaceName, schemaSetName)
+            mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED) >>
                     { throw thrownException }
+        and: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
         when: 'delete schema set endpoint is invoked'
-            def response = performDeleteRequest(schemaSetEndpoint)
+            def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
         then: 'schema set deletion fails with conflict response code'
             response.status == HttpStatus.CONFLICT.value()
     }
 
-    def performCreateDataspaceRequest(String dataspaceName) {
-        return mvc.perform(
-                post("$basePath/v1/dataspaces").param('dataspace-name', dataspaceName)
-        ).andReturn().response
+    def 'Get existing schema set.'() {
+        given: 'service method returns a new schema set'
+            mockCpsModuleService.getSchemaSet(dataspaceName, schemaSetName) >>
+                    new SchemaSet(name: schemaSetName, dataspaceName: dataspaceName)
+        and: 'an endpoint'
+            def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
+        when: 'get schema set API is invoked'
+            def response = mvc.perform(get(schemaSetEndpoint)).andReturn().response
+        then: 'the correct schema set is returned'
+            response.status == HttpStatus.OK.value()
+            response.getContentAsString().contains(schemaSetName)
+    }
+
+    def 'Create Anchor.'() {
+        given: 'request parameters'
+            def requestParams = new LinkedMultiValueMap<>()
+            requestParams.add('schema-set-name', schemaSetName)
+            requestParams.add('anchor-name', anchorName)
+        and: 'an endpoint'
+            def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
+        when: 'post is invoked'
+            def response = mvc.perform(post(anchorEndpoint).contentType(MediaType.APPLICATION_JSON)
+                    .params(requestParams as MultiValueMap)).andReturn().response
+        then: 'anchor is created successfully'
+            1 * mockCpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName)
+            response.status == HttpStatus.CREATED.value()
+            response.getContentAsString().contains(anchorName)
+    }
+
+    def 'Get existing anchor.'() {
+        given: 'service method returns a list of anchors'
+            mockCpsAdminService.getAnchors(dataspaceName) >> anchorList
+        and: 'an endpoint'
+            def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
+        when: 'get all anchors API is invoked'
+            def response = mvc.perform(get(anchorEndpoint)).andReturn().response
+        then: 'the correct anchor is returned'
+            response.status == HttpStatus.OK.value()
+            response.getContentAsString().contains(anchorName)
+    }
+
+    def 'Get existing anchor by dataspace and anchor name.'() {
+        given: 'service method returns an anchor'
+            mockCpsAdminService.getAnchor(dataspaceName,anchorName) >> new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName:schemaSetName)
+        and: 'an endpoint'
+            def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName"
+        when: 'get anchor API is invoked'
+            def response = mvc.perform(get(anchorEndpoint))
+                    .andReturn().response
+            def responseContent = response.getContentAsString()
+        then: 'the correct anchor is returned'
+            response.status == HttpStatus.OK.value()
+            responseContent.contains(anchorName)
+            responseContent.contains(dataspaceName)
+            responseContent.contains(schemaSetName)
     }
 
     def createMultipartFile(filename, content) {
@@ -192,51 +271,4 @@ class AdminRestControllerSpec extends Specification {
         multipartFile.getInputStream() >> { throw new IOException() }
         return multipartFile
     }
-
-    def performCreateSchemaSetRequest(multipartFile) {
-        return mvc.perform(
-                multipart("$basePath$schemaSetsEndpoint")
-                        .file(multipartFile)
-                        .param('schema-set-name', 'test-schema-set')
-        ).andReturn().response
-    }
-
-    def performDeleteRequest(String deleteEndpoint) {
-        return mvc.perform(delete("$basePath$deleteEndpoint")).andReturn().response
-    }
-
-    def 'Get existing schema set'() {
-        given:
-            mockCpsModuleService.getSchemaSet('test-dataspace', 'my_schema_set') >>
-                    new SchemaSet(name: 'my_schema_set', dataspaceName: 'test-dataspace')
-        when: 'get schema set API is invoked'
-            def response = mvc.perform(get("$basePath$schemaSetEndpoint")).andReturn().response
-        then: 'the correct schema set is returned'
-            response.status == HttpStatus.OK.value()
-            response.getContentAsString().contains('my_schema_set')
-    }
-
-    def 'Create Anchor'() {
-        given:
-            def requestParams = new LinkedMultiValueMap<>()
-            requestParams.add('schema-set-name', 'my_schema-set')
-            requestParams.add('anchor-name', 'my_anchor')
-        when: 'post is invoked'
-            def response = mvc.perform(post("$basePath$anchorsEndpoint").contentType(MediaType.APPLICATION_JSON)
-                    .params(requestParams as MultiValueMap)).andReturn().response
-        then: 'Anchor is created successfully'
-            1 * mockCpsAdminService.createAnchor('my_dataspace', 'my_schema-set', 'my_anchor')
-            response.status == HttpStatus.CREATED.value()
-            response.getContentAsString().contains('my_anchor')
-    }
-
-    def 'Get existing anchor'() {
-        given:
-            mockCpsAdminService.getAnchors('my_dataspace') >> anchorList
-        when: 'get all anchors API is invoked'
-            def response = mvc.perform(get("$basePath$anchorsEndpoint")).andReturn().response
-        then: 'the correct anchor is returned'
-            response.status == HttpStatus.OK.value()
-            response.getContentAsString().contains('my_anchor')
-    }
 }
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
new file mode 100644 (file)
index 0000000..bed3ba2
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.rest.controller
+
+import org.modelmapper.ModelMapper
+import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsModuleService
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Specification
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
+@WebMvcTest
+class DataRestControllerSpec extends Specification {
+
+    @SpringBean
+    CpsDataService mockCpsDataService = Mock()
+
+    @SpringBean
+    CpsModuleService mockCpsModuleService = Mock()
+
+    @SpringBean
+    CpsAdminService mockCpsAdminService = Mock()
+
+    @SpringBean
+    ModelMapper modelMapper = Mock()
+
+    @Autowired
+    MockMvc mvc
+
+    @Value('${rest.api.base-path}')
+    def basePath
+
+    def dataspaceName = 'my_dataspace'
+    def anchorName = 'my_anchor'
+
+    def 'Create a node.'() {
+        given:'an endpoint'
+            def nodeEndpoint ="$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes"
+            def json = 'some json (this is not validated)'
+        when: 'post is invoked'
+            def response = mvc.perform(post(nodeEndpoint).contentType(MediaType.APPLICATION_JSON).content(json))
+                    .andReturn().response
+        then: 'the java API is called with the correct parameters'
+            1 * mockCpsDataService.saveData(dataspaceName, anchorName, json)
+            response.status == HttpStatus.CREATED.value()
+    }
+}
\ No newline at end of file
index 45f6102..8b02d73 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.rest.exceptions
 import groovy.json.JsonSlurper
 import org.modelmapper.ModelMapper
 import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.spi.exceptions.AnchorAlreadyDefinedException
 import org.onap.cps.spi.exceptions.CpsException
@@ -55,6 +56,9 @@ class CpsRestExceptionHandlerSpec extends Specification {
     @SpringBean
     CpsModuleService mockCpsModuleService = Mock()
 
+    @SpringBean
+    CpsDataService mockCpsDataService = Mock()
+
     @SpringBean
     ModelMapper modelMapper = Mock()
 
index ebeeb9a..a8f4965 100644 (file)
 
 package org.onap.cps.api;
 
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.onap.cps.spi.exceptions.DataValidationException;
+
 /*
  * Datastore interface for handling CPS data.
  */
 public interface CpsDataService {
-
+    /**
+     * Persists data for the given anchor and dataspace.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorName    anchor name
+     * @param jsonData      json data
+     * @throws DataValidationException when json data is invalid
+     */
+    void saveData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String jsonData);
 }
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
new file mode 100644 (file)
index 0000000..2a1e18b
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  Modifications Copyright (C) 2020 Bell Canada. 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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl;
+
+import org.onap.cps.api.CpsAdminService;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsModuleService;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.utils.YangUtils;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CpsDataServiceImpl implements CpsDataService {
+
+    @Autowired
+    private CpsDataPersistenceService cpsDataPersistenceService;
+
+    @Autowired
+    private CpsAdminService cpsAdminService;
+
+    @Autowired
+    private CpsModuleService cpsModuleService;
+
+    @Autowired
+    private YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
+
+    @Override
+    public void saveData(final String dataspaceName, final String anchorName, final String jsonData) {
+        final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+        final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
+        final NormalizedNode normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext);
+        final DataNode dataNode = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).build();
+        cpsDataPersistenceService.storeDataNode(dataspaceName, anchor.getName(), dataNode);
+    }
+
+    private SchemaContext getSchemaContext(final String dataspaceName, final String schemaSetName) {
+        return yangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName).getSchemaContext();
+    }
+}
\ No newline at end of file
index 427ddd6..990b7bb 100644 (file)
@@ -40,9 +40,9 @@ public class CpsModuleServiceImpl implements CpsModuleService {
 
     @Override
     public void createSchemaSet(final String dataspaceName, final String schemaSetName,
-            final Map<String, String> yangResourcesNameToContentMap) {
+        final Map<String, String> yangResourcesNameToContentMap) {
         final YangTextSchemaSourceSet yangTextSchemaSourceSet
-                = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap);
+            = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap);
         cpsModulePersistenceService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap);
         yangTextSchemaSourceSetCache.updateCache(dataspaceName, schemaSetName, yangTextSchemaSourceSet);
     }
@@ -50,9 +50,9 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Override
     public SchemaSet getSchemaSet(final String dataspaceName, final String schemaSetName) {
         final YangTextSchemaSourceSet yangTextSchemaSourceSet = yangTextSchemaSourceSetCache
-                                                                        .get(dataspaceName, schemaSetName);
+            .get(dataspaceName, schemaSetName);
         return SchemaSet.builder().name(schemaSetName).dataspaceName(dataspaceName)
-                       .moduleReferences(yangTextSchemaSourceSet.getModuleReferences()).build();
+            .moduleReferences(yangTextSchemaSourceSet.getModuleReferences()).build();
     }
 
     @Override
index 1244d54..1ba9432 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.exceptions.DataValidationException;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
@@ -52,17 +53,21 @@ public class YangUtils {
      * @param schemaContext the SchemaContext for the given data
      * @return the NormalizedNode representing the json data
      */
-    public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext)
-            throws IOException {
+    public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext) {
         final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
                 .getShared(schemaContext);
         final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult();
         final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
                 .from(normalizedNodeResult);
-        try (final JsonParserStream jsonParserStream = JsonParserStream
-                .create(normalizedNodeStreamWriter, jsonCodecFactory)) {
-            final JsonReader jsonReader = new JsonReader(new StringReader(jsonData));
-            jsonParserStream.parse(jsonReader);
+        try {
+            try (final JsonParserStream jsonParserStream = JsonParserStream
+                    .create(normalizedNodeStreamWriter, jsonCodecFactory)) {
+                final JsonReader jsonReader = new JsonReader(new StringReader(jsonData));
+                jsonParserStream.parse(jsonReader);
+            }
+        } catch (final IOException e) {
+            throw new DataValidationException("Failed to parse json data.", String
+                .format("Exception occurred on parsing string %s.", jsonData), e);
         }
         return normalizedNodeResult.getResult();
     }
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
new file mode 100644 (file)
index 0000000..5874e27
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl
+
+import org.onap.cps.TestUtils
+import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsModuleService
+import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.spi.model.Anchor
+import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import spock.lang.Specification
+
+class CpsDataServiceImplSpec extends Specification {
+    def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
+    def mockCpsAdminService = Mock(CpsAdminService)
+    def mockCpsModuleService = Mock(CpsModuleService)
+    def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+
+    def objectUnderTest = new CpsDataServiceImpl()
+
+    def setup() {
+        objectUnderTest.cpsDataPersistenceService = mockCpsDataPersistenceService;
+        objectUnderTest.cpsAdminService = mockCpsAdminService;
+        objectUnderTest.cpsModuleService = mockCpsModuleService;
+        objectUnderTest.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache;
+    }
+
+    def dataspaceName = 'some dataspace'
+    def anchorName = 'some anchor'
+    def schemaSetName = 'some schema set'
+
+    def 'Saving json data.'() {
+        given: 'that the admin service will return an anchor'
+            def anchor = new Anchor()
+            anchor.name = anchorName
+            anchor.schemaSetName = schemaSetName
+            mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
+        and: 'the schema source set cache returns a schema source set'
+            def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+            mockYangTextSchemaSourceSetCache.get(dataspaceName,schemaSetName) >> mockYangTextSchemaSourceSet
+        and: 'the schema source sets returns the test-tree schema context'
+            def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
+            def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+            mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+        when: 'save data method is invoked with test-tree json data'
+            def jsonData = org.onap.cps.TestUtils.getResourceFileContent('test-tree.json')
+            objectUnderTest.saveData(dataspaceName, anchorName, jsonData)
+        then: 'the persistence service method is invoked with correct parameters'
+            1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
+                    { dataNode -> dataNode.xpath == '/test-tree' })
+    }
+}
\ No newline at end of file
index 5f2168a..261d174 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.api.impl
 
 import org.onap.cps.TestUtils
 import org.onap.cps.api.CpsAdminService
+import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.model.ModuleReference
@@ -46,6 +47,8 @@ class CpsModuleServiceImplSpec extends Specification {
     CpsModulePersistenceService mockModuleStoreService = Mock()
     @SpringBean
     CpsAdminService mockCpsAdminService = Mock()
+    @SpringBean
+    CpsDataPersistenceService mockDataPersistenceService = Mock()
     @Autowired
     CpsModuleServiceImpl objectUnderTest = new CpsModuleServiceImpl()
     @SpringBean
@@ -93,14 +96,14 @@ class CpsModuleServiceImplSpec extends Specification {
     }
 
     @Unroll
-    def 'Delete set by name and dataspace with #cascadeDeleteOption.'(){
+    def 'Delete set by name and dataspace with #cascadeDeleteOption.'() {
         when: 'schema set deletion is requested'
             objectUnderTest.deleteSchemaSet(dataspaceName, schemaSetname, cascadeDeleteOption)
         then: 'persistence service method is invoked with same parameters'
             mockModuleStoreService.deleteSchemaSet(dataspaceName, schemaSetname, cascadeDeleteOption)
         where: 'following parameters are used'
-            dataspaceName | schemaSetname | cascadeDeleteOption
-            'dataspace-1'  | 'schemas-set-1' | CASCADE_DELETE_ALLOWED
-            'dataspace-2'  | 'schemas-set-2' | CASCADE_DELETE_PROHIBITED
+            dataspaceName | schemaSetname   | cascadeDeleteOption
+            'dataspace-1' | 'schemas-set-1' | CASCADE_DELETE_ALLOWED
+            'dataspace-2' | 'schemas-set-2' | CASCADE_DELETE_PROHIBITED
     }
 }
index d6751bb..904e826 100755 (executable)
 package org.onap.cps.api.impl\r
 \r
 import org.onap.cps.TestUtils\r
+import org.onap.cps.api.CpsAdminService\r
+import org.onap.cps.spi.CpsDataPersistenceService\r
 import org.onap.cps.spi.CpsModulePersistenceService\r
+import org.onap.cps.spi.model.Anchor\r
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder\r
 import spock.lang.Specification\r
 \r
 class E2ENetworkSliceSpec extends Specification {\r
     def mockModuleStoreService = Mock(CpsModulePersistenceService)\r
+    def mockDataStoreService = Mock(CpsDataPersistenceService)\r
+    def mockCpsAdminService = Mock(CpsAdminService)\r
+    def cpsModuleServiceImpl = new CpsModuleServiceImpl()\r
+    def cpsDataServiceImple = new CpsDataServiceImpl()\r
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)\r
-    def objectUnderTest = new CpsModuleServiceImpl()\r
+\r
+    def dataspaceName = 'someDataspace'\r
+    def anchorName = 'someAnchor'\r
+    def schemaSetName = 'someSchemaSet'\r
 \r
     def setup() {\r
-        objectUnderTest.cpsModulePersistenceService = mockModuleStoreService\r
-        objectUnderTest.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache\r
+        cpsDataServiceImple.cpsDataPersistenceService = mockDataStoreService\r
+        cpsDataServiceImple.cpsAdminService = mockCpsAdminService\r
+        cpsDataServiceImple.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache\r
+        cpsModuleServiceImpl.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache\r
+        cpsModuleServiceImpl.cpsModulePersistenceService = mockModuleStoreService\r
     }\r
 \r
     def 'E2E model can be parsed by CPS.'() {\r
         given: 'Valid yang resource as name-to-content map'\r
             def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('e2e/basic/ietf-inet-types.yang','e2e/basic/ietf-yang-types.yang','e2e/basic/ran-network2020-08-06.yang')\r
         when: 'Create schema set method is invoked'\r
-            objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap)\r
+            cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap)\r
+        then: 'Parameters are validated and processing is delegated to persistence service'\r
+            1 * mockModuleStoreService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap)\r
+    }\r
+\r
+    def 'E2E Coverage Area-Tracking Area & TA-Cell mapping model can be parsed by CPS.'() {\r
+        given: 'Valid yang resource as name-to-content map'\r
+            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap(\r
+                    'e2e/basic/cps-cavsta-onap-internal2021-01-28.yang')\r
+        when: 'Create schema set method is invoked'\r
+            cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap)\r
+        then: 'Parameters are validated and processing is delegated to persistence service'\r
+            1 * mockModuleStoreService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap)\r
+    }\r
+\r
+    def 'E2E Coverage Area-Tracking Area & TA-Cell mapping data can be parsed by CPS.'() {\r
+        given: 'Valid yang resource as name-to-content map'\r
+            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap(\r
+                    'e2e/basic/cps-cavsta-onap-internal2021-01-28.yang')\r
+            def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()\r
+        and : 'a valid json is provided for the model'\r
+            def jsonData = TestUtils.getResourceFileContent('e2e/basic/Data.txt')\r
+        and : 'all the further dependencies are mocked '\r
+            mockCpsAdminService.getAnchor(dataspaceName, anchorName) >>\r
+                    new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).build()\r
+            mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >>\r
+                    YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)\r
+            mockModuleStoreService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext\r
+        when: 'saveData method is invoked'\r
+            cpsDataServiceImple.saveData(dataspaceName, anchorName, jsonData)\r
         then: 'Parameters are validated and processing is delegated to persistence service'\r
-            1 * mockModuleStoreService.storeSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap)\r
+            1 * mockDataStoreService.storeDataNode(dataspaceName, anchorName,\r
+                    {dataNode -> dataNode.xpath == '/ran-coverage-area' && dataNode.childDataNodes.size() == 4})\r
     }\r
 }\r
diff --git a/cps-service/src/test/resources/e2e/basic/Data.txt b/cps-service/src/test/resources/e2e/basic/Data.txt
new file mode 100644 (file)
index 0000000..c10c6d9
--- /dev/null
@@ -0,0 +1,42 @@
+{
+"ran-coverage-area":{
+  "pLMNIdList": [
+    {
+      "mcc": "310",
+      "mnc": "410"
+    },
+    {
+       "mcc": "2310",
+       "mnc": "2410"
+    }
+  ],
+  "coverage-area": [
+    {
+      "coverageArea": "Washington",
+      "coverageAreaTAList": [
+        {
+          "nRTAC": 234,
+          "taCellsList": [
+            {
+              "cellLocalId": 15709
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "coverageArea": "Dublin",
+      "coverageAreaTAList": [
+        {
+          "nRTAC": 2234,
+          "taCellsList": [
+            {
+              "cellLocalId": 15809
+            }
+          ]
+        }
+      ]
+    }
+  ]
+  }
+}
\ No newline at end of file
diff --git a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang
new file mode 100644 (file)
index 0000000..2a8a925
--- /dev/null
@@ -0,0 +1,153 @@
+module cps-cavsta-onap-internal {
+  yang-version 1.1;
+  namespace "org:onap:ccsdk:features:sdnr:northbound:cps-cavsta-onap-internal";
+  prefix onap-cavsta;
+
+    organization
+    "Open Network Automation Platform - ONAP
+     <https://www.onap.org>";
+  contact
+    "Editors:
+       Ă€hila Pandaram
+       <mailto:ahila.pandaram@wipro.com>
+
+       Swaminathan Seetharaman
+       <mailto:swaminathan.seetharaman@wipro.com>";
+  description
+    "This module contains YANG definitions for the relationship among coverage area, 
+     tracking area list and cells under each tracking area.
+     This relationship is used for internal purpose of ONAP to populate the details.
+
+    Copyright (C) 2020-2021 Wipro Limited.
+    
+    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.";
+
+  revision 2021-01-28 {
+    description
+      "RAN Network YANG Model for ONAP/O-RAN POC";
+    reference
+      "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
+  }
+
+   typedef Tac {
+    type int64 {
+      range "0..16777215";
+    }
+    description
+      "Tracking Area Code";
+    reference
+      "TS 23.003 clause 19.4.2.3";
+  }  
+   
+
+  typedef Mcc {
+    type string;
+    description
+      "The mobile country code consists of three decimal digits,
+       The first digit of the mobile country code identifies the geographic
+       region (the digits 1 and 8 are not used):";
+    reference
+      "3GPP TS 23.003 subclause 2.2 and 12.1";
+  }
+
+  typedef Mnc {
+    type string;
+    description
+      "The mobile network code consists of two or three
+       decimal digits (for example: MNC of 001 is not the same as MNC of 01)";
+    reference
+      "3GPP TS 23.003 subclause 2.2 and 12.1";
+  }
+
+          
+    grouping trackingAreaGroup{
+                leaf nRTAC {
+      type Tac;
+      description "Identity of the common Tracking Area Code for the PLMNs
+        allowedValues:
+        a) It is the TAC or Extended-TAC. 
+        b) A cell can only broadcast one TAC or Extended-TAC. 
+          See TS 36.300, subclause 10.1.7 (PLMNID and TAC relation).
+        c) TAC is defined in subclause 19.4.2.3 of 3GPP TS 23.003 and 
+          Extended-TAC is defined in subclause 9.3.1.29 of 3GPP TS 38.473.
+        d) For a 5G SA (Stand Alone), it has a non-null value.";
+    }
+               list taCellsList{
+        key cellLocalId;
+        leaf cellLocalId {
+      description "Identifies an NR cell of a gNB. Together with corresponding
+        gNB ID it forms the NR Cell Identifier (NCI).";
+        mandatory true;
+        type int32 { range "0..16383"; }
+    }
+    }
+       }
+    
+    grouping PLMNId {
+    description
+      "It specifies the PLMN identifier to be used as part of the global RAN node identity";
+    reference
+      "TS 23.658";
+    leaf mcc {
+      type Mcc;
+      mandatory true;
+      description
+        "The mobile country code consists of three decimal digits,
+       The first digit of the mobile country code identifies the geographic
+       region (the digits 1 and 8 are not used)";
+    }
+    leaf mnc {
+      type Mnc;
+      mandatory true;
+      description
+        "The mobile network code consists of two or three
+       decimal digits (for example: MNC of 001 is not the same as MNC of 01)";
+    }
+  }
+
+    
+    grouping coverageAreaGroup{
+      leaf coverageArea{
+      description "An attribute specifies the coverage area of the network slice, 
+      i.e. the geographic region where a 3GPP communication service is accessible,
+      see Table 7.1-1 of TS 22.261 [28]) and NG.116 [50].";
+      type string;
+    }
+    
+      list coverageAreaTAList{
+       uses trackingAreaGroup;
+       key "nRTAC";
+       description "This list contains the tracking area list for the coverageArea";
+    }
+    }
+    
+    container ran-coverage-area{
+    
+      list pLMNIdList {
+      description "List of at most six entries of PLMN Identifiers, but at least 
+        one (the primary PLMN Id).
+        The PLMN Identifier is composed of a Mobile Country Code (MCC) and a 
+        Mobile Network Code (MNC).";
+      key "mcc mnc";
+      uses PLMNId;
+    }
+
+      
+      list coverage-area{
+        uses coverageAreaGroup;
+        key "coverageArea";
+        description "This list contains the list of coverage area of a PLMNID";
+      }
+    
+    }
+    }