summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--checkstyle/pom.xml52
-rw-r--r--checkstyle/src/main/CopyrightCheck.py262
-rw-r--r--checkstyle/src/main/resources/copyright-template.txt16
-rw-r--r--checkstyle/src/main/resources/ignore-files-config.csv6
-rw-r--r--checkstyle/src/main/resources/project-committers-config.csv3
-rw-r--r--checkstyle/src/main/test_CopyrightCheck.py441
-rw-r--r--cps-ncmp-rest/docs/openapi/components.yaml48
-rwxr-xr-xcps-ncmp-rest/docs/openapi/ncmp-inventory.yml7
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java60
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy87
-rw-r--r--cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json6
-rw-r--r--cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json2
-rw-r--r--cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json2
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java95
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java11
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java3
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java70
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java7
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy307
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy4
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy12
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy25
-rw-r--r--cps-rest/docs/openapi/cpsAdmin.yml2
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy7
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/CpsAdminService.java2
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java15
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java7
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java11
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java13
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java7
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java2
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java4
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java51
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy6
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy48
-rw-r--r--csit/tests/cps-model-sync/cps-model-sync.robot4
-rw-r--r--docs/cps-path.rst150
-rwxr-xr-xdocs/release-notes.rst3
40 files changed, 1565 insertions, 301 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml
index 4e042e97d..bd343683b 100644
--- a/checkstyle/pom.xml
+++ b/checkstyle/pom.xml
@@ -2,6 +2,7 @@
<!--
============LICENSE_START=======================================================
Copyright (C) 2020 Pantheon.tech
+ Modifications Copyright (C) 2022 Nordix Foundation
================================================================================
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,6 +28,31 @@
<artifactId>checkstyle</artifactId>
<version>3.1.0-SNAPSHOT</version>
+ <profiles>
+ <profile>
+ <id>Windows</id>
+ <activation>
+ <os>
+ <family>Windows</family>
+ </os>
+ </activation>
+ <properties>
+ <script.executor>python</script.executor>
+ </properties>
+ </profile>
+ <profile>
+ <id>unix</id>
+ <activation>
+ <os>
+ <family>unix</family>
+ </os>
+ </activation>
+ <properties>
+ <script.executor>python3</script.executor>
+ </properties>
+ </profile>
+ </profiles>
+
<properties>
<nexusproxy>https://nexus.onap.org</nexusproxy>
<releaseNexusPath>/content/repositories/releases/</releaseNexusPath>
@@ -44,6 +70,32 @@
</plugin>
</plugins>
</pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ <executions>
+ <execution>
+ <id>copyright-check</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <executable>${script.executor}</executable>
+ <workingDirectory>../checkstyle/src/main/</workingDirectory>
+ <arguments>
+ <argument>CopyrightCheck.py</argument>
+ <argument>resources/project-committers-config.csv</argument>
+ <argument>resources/copyright-template.txt</argument>
+ <argument>resources/ignore-files-config.csv</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
</build>
<distributionManagement>
diff --git a/checkstyle/src/main/CopyrightCheck.py b/checkstyle/src/main/CopyrightCheck.py
new file mode 100644
index 000000000..8f1dbff57
--- /dev/null
+++ b/checkstyle/src/main/CopyrightCheck.py
@@ -0,0 +1,262 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2022 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=========================================================
+
+import subprocess
+import csv
+import re
+import datetime
+
+#constants
+import sys
+
+COMMITTERS_CONFIG_FILE = ''
+TEMPLATE_COPYRIGHT_FILE = ''
+IGNORE_FILE = ''
+if len(sys.argv) == 4:
+ COMMITTERS_CONFIG_FILE = sys.argv[1]
+ TEMPLATE_COPYRIGHT_FILE = sys.argv[2]
+ IGNORE_FILE = sys.argv[3]
+
+BANNER = '=' * 120
+
+def main():
+ print(BANNER + '\nCopyright Check Python Script:')
+ PermissionsCheck()
+
+ committerEmailExtension = GetCommitterEmailExtension()
+ projectCommitters = ReadProjectCommittersConfigFile()
+
+ CheckCommitterInConfigFile(committerEmailExtension, projectCommitters)
+
+ alteredFiles = FindAlteredFiles()
+
+ if alteredFiles:
+ issueCounter = CheckCopyrightForFiles(alteredFiles, projectCommitters, committerEmailExtension)
+ else:
+ issueCounter = 0
+
+ print(str(issueCounter) + ' issue(s) found after '+ str(len(alteredFiles)) + ' altered file(s) checked')
+ print(BANNER)
+
+
+# Check that Script has access to command line functions to use git
+def PermissionsCheck():
+ if 'permission denied' in subprocess.run('git', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').lower():
+ print('Error, I may not have the necessary permissions. Exiting...')
+ print(BANNER)
+ sys.exit()
+ else:
+ return
+
+# Returns List of Strings of file tracked by git which have been changed/added
+def FindAlteredFiles():
+ ignoreFilePaths = GetIgnoredFiles()
+
+ #Before Stage lower case d removes deleted files
+ stream = subprocess.run('git diff --name-only --diff-filter=d', shell=True, stdout=subprocess.PIPE)
+ fileNames = stream.stdout.decode('utf-8')
+ #Staged
+ stream = subprocess.run('git diff --name-only --cached --diff-filter=d', shell=True, stdout=subprocess.PIPE)
+ fileNames += '\n' + stream.stdout.decode('utf-8')
+ #New committed
+ stream = subprocess.run('git diff --name-only HEAD^ HEAD --diff-filter=d', shell=True, stdout=subprocess.PIPE)
+ fileNames += '\n' + stream.stdout.decode('utf-8')
+
+ #String to list of strings
+ alteredFiles = fileNames.split("\n")
+
+ #Remove duplicates
+ alteredFiles = list(dict.fromkeys(alteredFiles))
+
+ #Remove blank string(s)
+ alteredFiles = list(filter(None, alteredFiles))
+
+ #Remove ignored-extensions
+ alteredFiles = list(filter(lambda fileName: not re.match("|".join(ignoreFilePaths), fileName), alteredFiles))
+
+ return alteredFiles
+
+# Get the email of the most recent committer
+def GetCommitterEmailExtension():
+ email = subprocess.run('git show -s --format=\'%ce\'', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').rstrip('\n')
+ return email[email.index('@'):]
+
+# Read the config file with names of companies and respective email extensions
+def ReadProjectCommittersConfigFile():
+ try:
+ with open(COMMITTERS_CONFIG_FILE, 'r') as file:
+ reader = csv.reader(file, delimiter=',')
+ projectCommitters = {row[0]:row[1] for row in reader}
+ projectCommitters.pop('email') #Remove csv header
+ except FileNotFoundError:
+ print('Unable to open Project Committers Config File, have the command line arguments been set?')
+ print(BANNER)
+ sys.exit()
+ return projectCommitters
+
+def CheckCommitterInConfigFile(committerEmailExtension, projectCommitters):
+ if not committerEmailExtension in projectCommitters:
+ print('Error, Committer email is not included in config file.')
+ print('If your company is new to the project please make appropriate changes to project-committers-config.csv')
+ print('for Copyright Check to work.')
+ print('Exiting...')
+ print(BANNER)
+ sys.exit()
+ else:
+ return True
+
+# Read config file with list of files to ignore
+def GetIgnoredFiles():
+ try:
+ with open(IGNORE_FILE, 'r') as file:
+ reader = csv.reader(file)
+ ignoreFilePaths = [row[0] for row in reader]
+ ignoreFilePaths.pop(0) #Remove csv header
+ ignoreFilePaths = [filePath.replace('*', '.*') for filePath in ignoreFilePaths]
+ except FileNotFoundError:
+ print('Unable to open File Ignore Config File, have the command line arguments been set?')
+ print(BANNER)
+ sys.exit()
+ return ignoreFilePaths
+
+# Read the template copyright file
+def GetCopyrightTemplate():
+ try:
+ with open(TEMPLATE_COPYRIGHT_FILE, 'r') as file:
+ copyrightTemplate = file.readlines()
+ except FileNotFoundError:
+ print('Unable to open Template Copyright File, have the command line arguments been set?')
+ print(BANNER)
+ sys.exit()
+ return copyrightTemplate
+
+def GetProjectRootDir():
+ return subprocess.run('git rev-parse --show-toplevel', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').rstrip('\n') + '/'
+
+# Get the Copyright from the altered file
+def ParseFileCopyright(fileObject):
+ global issueCounter
+ copyrightFlag = False
+ copyrightInFile = {}
+ lineNumber = 1
+ for line in fileObject:
+ if 'LICENSE_START' in line:
+ copyrightFlag = True
+ if copyrightFlag:
+ copyrightInFile[lineNumber] = line
+ if 'LICENSE_END' in line:
+ break
+ lineNumber += 1
+
+ if not copyrightFlag:
+ print(fileObject.name + ' | no copyright found')
+ return {}, {}
+
+ copyrightSignatures = {}
+ copyrightLineNumbers = list(copyrightInFile.keys())
+ #Capture signature lines after LICENSE_START line
+ for lineNumber in copyrightLineNumbers:
+ if '=' not in copyrightInFile[lineNumber]:
+ copyrightSignatures[lineNumber] = copyrightInFile[lineNumber]
+ copyrightInFile.pop(lineNumber)
+ elif 'LICENSE_START' not in copyrightInFile[lineNumber]:
+ break
+
+ return (copyrightInFile, copyrightSignatures)
+
+# Remove the Block comment syntax
+def RemoveCommentBlock(fileCopyright):
+ # Comment Characters can very depending on file # *..
+ endOfCommentsIndex = list(fileCopyright.values())[0].index('=')
+ for key in fileCopyright:
+ fileCopyright[key] = fileCopyright[key][endOfCommentsIndex:]
+ if fileCopyright[key] == '':
+ fileCopyright[key] = '\n'
+
+ return fileCopyright
+
+def CheckCopyrightForFiles(alteredFiles, projectCommitters, committerEmailExtension):
+ issueCounter = 0
+ templateCopyright = GetCopyrightTemplate() #Get Copyright Template
+ projectRootDir = GetProjectRootDir()
+
+ for fileName in alteredFiles: # Not removed files
+ try:
+ with open(projectRootDir + fileName, 'r') as fileObject:
+ (fileCopyright, fileSignatures) = ParseFileCopyright(fileObject)
+
+ #Empty dict evaluates to false
+ if fileCopyright and fileSignatures:
+ fileCopyright = RemoveCommentBlock(fileCopyright)
+ issueCounter += CheckCopyrightFormat(fileCopyright, templateCopyright, projectRootDir + fileName)
+ committerCompany = projectCommitters[committerEmailExtension]
+ issueCounter += CheckCopyrightSignature(fileSignatures, committerCompany, projectRootDir + fileName)
+ else:
+ issueCounter += 1
+
+ except FileNotFoundError:
+ issueCounter += 1
+ print('Unable to find file ' + projectRootDir + fileName)
+ return issueCounter
+
+# Check that the filecopyright matches the template copyright and print comparison
+def CheckCopyrightFormat(copyrightInFile, templateCopyright, filePath):
+ issueCounter = 0
+ errorWithComparison = ''
+ for copyrightInFileKey, templateLine in zip(copyrightInFile, templateCopyright):
+ if copyrightInFile[copyrightInFileKey] != templateLine:
+ issueCounter += 1
+ errorWithComparison += filePath + ' | line ' + '{:2}'.format(copyrightInFileKey) + ' read \t ' + repr(copyrightInFile[copyrightInFileKey]) + '\n'
+ errorWithComparison += filePath + ' | line ' + '{:2}'.format(copyrightInFileKey) + ' expected ' + repr(templateLine) + '\n'
+ if errorWithComparison != '':
+ print(errorWithComparison.rstrip('\n'))
+ return issueCounter
+
+# Check the signatures and compare with committer signature and current year
+def CheckCopyrightSignature(copyrightSignatures, committerCompany, filePath):
+ issueCounter = 0
+ errorWithSignature = ''
+ signatureExists = False #signatureExistsForCommitter
+ afterFirstLine = False #afterFirstCopyright
+ for key in copyrightSignatures:
+ if afterFirstLine and 'Modifications Copyright' not in copyrightSignatures[key]:
+ issueCounter += 1
+ errorWithSignature += filePath + ' | line ' + str(key) + ' expected Modifications Copyright\n'
+ elif not afterFirstLine and 'Copyright' not in copyrightSignatures[key]:
+ issueCounter += 1
+ errorWithSignature += filePath + ' | line ' + str(key) + ' expected Copyright\n'
+ if committerCompany in copyrightSignatures[key]:
+ signatureExists = True
+ signatureYear = int(re.findall(r'\d+', copyrightSignatures[key])[-1])
+ currentYear = datetime.date.today().year
+ if signatureYear != currentYear:
+ issueCounter += 1
+ errorWithSignature += filePath + ' | line ' + str(key) + ' update year to include ' + str(currentYear) + '\n'
+ afterFirstLine = True
+
+ if not signatureExists:
+ issueCounter += 1
+ errorWithSignature += filePath + ' | missing company name and year for ' + committerCompany
+
+ if errorWithSignature != '':
+ print(errorWithSignature.rstrip('\n'))
+
+ return issueCounter
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/checkstyle/src/main/resources/copyright-template.txt b/checkstyle/src/main/resources/copyright-template.txt
new file mode 100644
index 000000000..205e0caac
--- /dev/null
+++ b/checkstyle/src/main/resources/copyright-template.txt
@@ -0,0 +1,16 @@
+============LICENSE_START=======================================================
+================================================================================
+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=========================================================
diff --git a/checkstyle/src/main/resources/ignore-files-config.csv b/checkstyle/src/main/resources/ignore-files-config.csv
new file mode 100644
index 000000000..4f7394fbb
--- /dev/null
+++ b/checkstyle/src/main/resources/ignore-files-config.csv
@@ -0,0 +1,6 @@
+file path
+*checkstyle/*
+*.json
+*.yang
+*.rst
+*.csv \ No newline at end of file
diff --git a/checkstyle/src/main/resources/project-committers-config.csv b/checkstyle/src/main/resources/project-committers-config.csv
new file mode 100644
index 000000000..85ee43bda
--- /dev/null
+++ b/checkstyle/src/main/resources/project-committers-config.csv
@@ -0,0 +1,3 @@
+email,signature
+@est.tech,Nordix Foundation
+@bell.ca,Bell Canada \ No newline at end of file
diff --git a/checkstyle/src/main/test_CopyrightCheck.py b/checkstyle/src/main/test_CopyrightCheck.py
new file mode 100644
index 000000000..177f9d4a5
--- /dev/null
+++ b/checkstyle/src/main/test_CopyrightCheck.py
@@ -0,0 +1,441 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2022 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=========================================================
+
+import datetime
+import sys
+import unittest
+from unittest import mock
+from unittest.mock import MagicMock
+import io
+
+import CopyrightCheck
+
+BANNER = '=' * 120
+
+def MockStdout(command):
+ mock_stdout = MagicMock()
+ mock_stdout.configure_mock(**{"stdout.decode.return_value": command})
+ return mock_stdout
+
+class TestCopyrightCheck(unittest.TestCase):
+
+ @mock.patch('subprocess.run')
+ def test_PermissionsCheckFalse(self, mock_subprocess_run):
+ mock_subprocess_run.return_value = MockStdout('Permission denied')
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with self.assertRaises(SystemExit):
+ CopyrightCheck.PermissionsCheck()
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(),
+ 'Error, I may not have the necessary permissions. Exiting...\n' + BANNER + '\n')
+
+ @mock.patch('subprocess.run')
+ def test_PermissionsCheckTrue(self, mock_subprocess_run):
+ mock_subprocess_run.return_value = MockStdout(
+ 'usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]...')
+ CopyrightCheck.PermissionsCheck() # Assert no error thrown
+
+ @mock.patch('CopyrightCheck.GetIgnoredFiles')
+ @mock.patch('subprocess.run')
+ def test_FindAlteredFiles(self, mock_subprocess_run, mock_GetIgnoredFiles):
+ mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*']
+ mock_subprocess_run.return_value = MockStdout('File1.json\nFile2.java\nFile2.java\ndir/File3.java')
+ result = CopyrightCheck.FindAlteredFiles()
+ # Duplicates, .json and files in 'dir' removed
+ self.assertEqual(result, ['File2.java'])
+
+ @mock.patch('CopyrightCheck.GetIgnoredFiles')
+ @mock.patch('subprocess.run')
+ def test_FindAlteredFilesWithNoFileChanges(self, mock_subprocess_run, mock_GetIgnoredFiles):
+ mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*']
+ mock_subprocess_run.return_value = MockStdout('File1.json\ndir/File3.java')
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.FindAlteredFiles()
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(result, [])
+ self.assertEqual(capturedOutput.getvalue(), '')
+
+ @mock.patch('subprocess.run')
+ def test_GetCommitterEmailExtension(self, mock_subprocess_run):
+ mock_subprocess_run.return_value = MockStdout('a.committer.name@address.com')
+ result = CopyrightCheck.GetCommitterEmailExtension()
+ self.assertEqual(result, '@address.com')
+
+ def test_ReadProjectCommittersConfigFile(self):
+ mock_open = mock.mock_open(read_data="email,signature\n@address.com,Company Name")
+ with mock.patch('builtins.open', mock_open):
+ result = CopyrightCheck.ReadProjectCommittersConfigFile()
+ self.assertEqual(result, {'@address.com': 'Company Name'})
+
+ @mock.patch('CopyrightCheck.open')
+ def test_ReadProjectCommittersConfigFileError(self, mock_OpenFile):
+ mock_OpenFile.side_effect = FileNotFoundError
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with self.assertRaises(SystemExit):
+ CopyrightCheck.ReadProjectCommittersConfigFile()
+ sys.stdout = sys.__stdout__
+ expectedOutput = ('Unable to open Project Committers Config File, have the command line arguments been set?\n' +
+ BANNER + '\n')
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ def test_CheckCommitterInConfigFileTrue(self):
+ committerEmailExtension = '@address.com'
+ projectCommitters = {'@address.com': 'Company Name'}
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters)
+ sys.stdout = sys.__stdout__
+ self.assertTrue(result)
+ self.assertEqual(capturedOutput.getvalue(), "")
+
+ def test_CheckCommitterInConfigFileFalse(self):
+ committerEmailExtension = '@address.com'
+ projectCommitters = {'@anotheraddress.com': 'Another Company Name'}
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with self.assertRaises(SystemExit):
+ CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters)
+ sys.stdout = sys.__stdout__
+ expectedOutput = ('Error, Committer email is not included in config file.\n' +
+ 'If your company is new to the project please make appropriate changes to project-committers-config.csv\n' +
+ 'for Copyright Check to work.\n' +
+ 'Exiting...\n' + BANNER + '\n')
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ def test_GetIgnoredFiles(self):
+ mock_open = mock.mock_open(read_data="file path\n*checkstyle/*\n*.json")
+ with mock.patch('builtins.open', mock_open):
+ result = CopyrightCheck.GetIgnoredFiles()
+ self.assertEqual(result, [".*checkstyle/.*", ".*.json"])
+
+ @mock.patch('CopyrightCheck.open')
+ def test_GetIgnoredFilesError(self, mock_OpenFile):
+ mock_OpenFile.side_effect = FileNotFoundError
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with self.assertRaises(SystemExit):
+ CopyrightCheck.GetIgnoredFiles()
+ sys.stdout = sys.__stdout__
+ expectedOutput = ('Unable to open File Ignore Config File, have the command line arguments been set?\n' +
+ BANNER + '\n')
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ def test_GetCopyrightTemplate(self):
+ mock_open = mock.mock_open(read_data="****\nThis is a\nCopyright File\n****")
+ with mock.patch('builtins.open', mock_open):
+ result = CopyrightCheck.GetCopyrightTemplate()
+ self.assertEqual(result, ["****\n", "This is a\n", "Copyright File\n", "****"])
+
+ @mock.patch('CopyrightCheck.open')
+ def test_GetCopyrightTemplateError(self, mock_OpenFile):
+ mock_OpenFile.side_effect = FileNotFoundError
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with self.assertRaises(SystemExit):
+ CopyrightCheck.GetCopyrightTemplate()
+ sys.stdout = sys.__stdout__
+ expectedOutput = ('Unable to open Template Copyright File, have the command line arguments been set?\n' +
+ BANNER + '\n')
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ @mock.patch('subprocess.run')
+ def test_GetProjectRootDir(self, mock_subprocess_run):
+ mock_subprocess_run.return_value = MockStdout('project/root/dir\n')
+ result = CopyrightCheck.GetProjectRootDir()
+ self.assertEqual(result, 'project/root/dir/')
+
+
+ def test_ParseFileCopyright(self):
+ readFromFile = ["#Before lines will not be included\n",
+ "#===LICENSE_START===\n",
+ "#Copyright (C) 0000 Some Company\n",
+ "#A line without signature\n",
+ "#===============================\n",
+ "#This is the start of the Copyright\n",
+ "#===LICENSE_END===\n",
+ "After lines will not be included"]
+ copyright, signatures = CopyrightCheck.ParseFileCopyright(readFromFile)
+ self.assertEqual(copyright, {2: "#===LICENSE_START===\n",
+ 5: "#===============================\n",
+ 6: "#This is the start of the Copyright\n",
+ 7: "#===LICENSE_END===\n"})
+ self.assertEqual(signatures, {3: "#Copyright (C) 0000 Some Company\n",
+ 4: "#A line without signature\n"})
+
+ def test_ParseFileCopyrightNoCopyright(self):
+ fileObject = io.StringIO("#This is not\na copyright\n")
+ fileObject.name = 'some/file/name'
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ copyright, signatures = CopyrightCheck.ParseFileCopyright(fileObject)
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(copyright, {})
+ self.assertEqual(signatures, {})
+ self.assertEqual(capturedOutput.getvalue(), 'some/file/name | no copyright found\n')
+
+ def test_RemoveCommentBlock(self):
+ commentCharactersList = ['# ', '* ', '# ', '* ']
+
+ for commentCharacters in commentCharactersList:
+ copyright = {1: commentCharacters + '===LICENSE_START===\n',
+ 2: '\n',
+ 3: commentCharacters + 'This is the License\n',
+ 4: commentCharacters + '===LICENSE_END===\n'}
+ result = CopyrightCheck.RemoveCommentBlock(copyright)
+ self.assertEqual(result, {1: '===LICENSE_START===\n',
+ 2: '\n',
+ 3: 'This is the License\n',
+ 4: '===LICENSE_END===\n'})
+
+ @mock.patch('CopyrightCheck.open')
+ @mock.patch('CopyrightCheck.GetProjectRootDir')
+ @mock.patch('CopyrightCheck.GetCopyrightTemplate')
+ def test_CheckCopyrightForFileNotFound(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir, mock_OpenFile):
+ mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
+ mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
+ mock_OpenFile.side_effect = FileNotFoundError
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, [])
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(), 'Unable to find file some/project/root/dir/some-file.java\n')
+ self.assertEqual(result, 1)
+
+ @mock.patch('CopyrightCheck.ParseFileCopyright')
+ @mock.patch('CopyrightCheck.GetProjectRootDir')
+ @mock.patch('CopyrightCheck.GetCopyrightTemplate')
+ def test_CheckCopyrightForFileWithNoCopyright(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir,
+ mock_ParseFileCopyright):
+ mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
+ mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
+ mock_ParseFileCopyright.return_value = ({}, {})
+ mock_open = mock.mock_open(read_data="some-file-content")
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with mock.patch('builtins.open', mock_open):
+ result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, [])
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(), "")
+ self.assertEqual(result, 1)
+
+
+ @mock.patch('CopyrightCheck.CheckCopyrightSignature')
+ @mock.patch('CopyrightCheck.CheckCopyrightFormat')
+ @mock.patch('CopyrightCheck.ParseFileCopyright')
+ @mock.patch('CopyrightCheck.GetProjectRootDir')
+ @mock.patch('CopyrightCheck.GetCopyrightTemplate')
+ def test_CheckCopyrightForFilesWhichAreRight(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir,
+ mock_ParseFileCopyright, mock_CheckCopyrightFormat,
+ mock_CheckCopyrightSignature):
+ mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
+ mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
+ mock_ParseFileCopyright.return_value = ({1: '# =some-copyright-line'}, {2: '# =some-signature-line'})
+ mock_open = mock.mock_open(read_data="# =some-file-content")
+ mock_CheckCopyrightFormat.return_value = 0
+ mock_CheckCopyrightSignature.return_value = 0
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ with mock.patch('builtins.open', mock_open):
+ result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java', 'another-file.java'], {'@address.com': 'Some Company'}, '@address.com')
+ sys.stdout = sys.__stdout__
+ self.assertEqual(result, 0)
+ self.assertEqual(capturedOutput.getvalue(), "")
+
+ mock_GetCopyrightTemplate.assert_called_once_with()
+ mock_GetProjectRootDir.assert_called_once_with()
+ self.assertEqual(mock_ParseFileCopyright.call_count, 2)
+ mock_CheckCopyrightFormat.assert_has_calls([
+ mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/some-file.java'),
+ mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/another-file.java')
+ ])
+ mock_CheckCopyrightSignature.assert_has_calls([
+ mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/some-file.java'),
+ mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/another-file.java')
+ ])
+ self.assertEqual(mock_CheckCopyrightFormat.call_count, 2)
+ self.assertEqual(mock_CheckCopyrightSignature.call_count, 2)
+
+
+ def test_CheckCopyrightFormatWhichIsWrong(self):
+ fileCopyright = {1: '---LICENSE_START---\n',
+ 2: 'This is the license typo\n',
+ 3: '',
+ 4: '===license_end===\n'}
+ templateCopyright = ['===LICENSE_START===\n',
+ 'This is the license\n',
+ '\n',
+ '===LICENSE_END===\n']
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path')
+ sys.stdout = sys.__stdout__
+
+ expectedOutput = ("some/file/path | line 1 read \t '---LICENSE_START---\\n'\n" +
+ "some/file/path | line 1 expected '===LICENSE_START===\\n'\n" +
+ "some/file/path | line 2 read \t 'This is the license typo\\n'\n" +
+ "some/file/path | line 2 expected 'This is the license\\n'\n" +
+ "some/file/path | line 3 read \t ''\n" +
+ "some/file/path | line 3 expected '\\n'\n" +
+ "some/file/path | line 4 read \t '===license_end===\\n'\n" +
+ "some/file/path | line 4 expected '===LICENSE_END===\\n'\n")
+
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+ self.assertEqual(result, 4)
+
+ def test_CheckCopyrightFormatWhichIsCorrect(self):
+ fileCopyright = {1: '===LICENSE_START===\n',
+ 2: 'This is the license\n',
+ 3: '\n',
+ 4: '===LICENSE_END===\n'}
+ templateCopyright = ['===LICENSE_START===\n',
+ 'This is the license\n',
+ '\n',
+ '===LICENSE_END===\n']
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path')
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(), "")
+ self.assertEqual(result, 0)
+
+ def test_CheckCopyrightSignatureWhichIsWrong(self):
+ fileSignatures = {1: "Trigger expected Copy-right",
+ 2: "Trigger expected Mod Copy-right"}
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
+ sys.stdout = sys.__stdout__
+
+ expectedOutput = ("some/file/path | line 1 expected Copyright\n" +
+ "some/file/path | line 2 expected Modifications Copyright\n" +
+ "some/file/path | missing company name and year for Some-Company\n")
+
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+ self.assertEqual(result, 3)
+
+ def test_CheckCopyrightSignatureWhichHasWrongYear(self):
+ currentYear = datetime.date.today().year
+ fileSignatures = {1: "Copyright (C) 1999 Some-Company"}
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(),
+ "some/file/path | line 1 update year to include " + str(currentYear) + "\n")
+ self.assertEqual(result, 1)
+
+ def test_CheckCopyrightSignatureWhichIsRight(self):
+ currentYear = datetime.date.today().year
+ fileSignatures = {1: "Copyright (C) " + str(currentYear) + " Some-Company"}
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+ result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
+ sys.stdout = sys.__stdout__
+
+ self.assertEqual(capturedOutput.getvalue(), "")
+ self.assertEqual(result, 0)
+
+ @mock.patch('CopyrightCheck.CheckCopyrightForFiles')
+ @mock.patch('CopyrightCheck.FindAlteredFiles')
+ @mock.patch('CopyrightCheck.CheckCommitterInConfigFile')
+ @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile')
+ @mock.patch('CopyrightCheck.GetCommitterEmailExtension')
+ @mock.patch('CopyrightCheck.PermissionsCheck')
+ def test_Main(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile,
+ mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles):
+
+ mock_GetCommitterEmailExtension.return_value = '@address.com'
+ mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'}
+ mock_FindAlteredFiles.return_value = ['some-file.java']
+ mock_CheckCopyrightForFiles.return_value = 5
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+
+ CopyrightCheck.main()
+
+ sys.stdout = sys.__stdout__
+
+ expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' +
+ '5 issue(s) found after 1 altered file(s) checked\n' +
+ BANNER + '\n')
+
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ mock_PermissionsCheck.assert_called_once_with()
+ mock_GetCommitterEmailExtension.assert_called_once_with()
+ mock_ReadProjectCommittersConfigFile.assert_called_once_with()
+ mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'})
+ mock_FindAlteredFiles.assert_called_once_with()
+ mock_CheckCopyrightForFiles.assert_called_once_with(['some-file.java'], {'@address.com', 'Some Company'}, '@address.com')
+
+ @mock.patch('CopyrightCheck.CheckCopyrightForFiles')
+ @mock.patch('CopyrightCheck.FindAlteredFiles')
+ @mock.patch('CopyrightCheck.CheckCommitterInConfigFile')
+ @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile')
+ @mock.patch('CopyrightCheck.GetCommitterEmailExtension')
+ @mock.patch('CopyrightCheck.PermissionsCheck')
+ def test_MainNoFiles(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile,
+ mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles):
+
+ mock_GetCommitterEmailExtension.return_value = '@address.com'
+ mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'}
+ mock_FindAlteredFiles.return_value = []
+
+ capturedOutput = io.StringIO()
+ sys.stdout = capturedOutput # Capture output to stdout
+
+ CopyrightCheck.main()
+
+ sys.stdout = sys.__stdout__
+
+ expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' +
+ '0 issue(s) found after 0 altered file(s) checked\n' +
+ BANNER + '\n')
+
+ self.assertEqual(capturedOutput.getvalue(), expectedOutput)
+
+ mock_PermissionsCheck.assert_called_once_with()
+ mock_GetCommitterEmailExtension.assert_called_once_with()
+ mock_ReadProjectCommittersConfigFile.assert_called_once_with()
+ mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'})
+ mock_FindAlteredFiles.assert_called_once_with()
+ mock_CheckCopyrightForFiles.assert_not_called()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml
index 092c0a28b..771919354 100644
--- a/cps-ncmp-rest/docs/openapi/components.yaml
+++ b/cps-ncmp-rest/docs/openapi/components.yaml
@@ -86,6 +86,54 @@ components:
items:
type: string
example: [my-cm-handle1, my-cm-handle2, my-cm-handle3]
+ DmiPluginRegistrationErrorResponse:
+ type: object
+ properties:
+ failedCreatedCmHandles:
+ type: array
+ items:
+ $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+ example: [
+ {
+ "cmHandle": "my-cm-handle-01",
+ "errorCode": "01",
+ "errorText": "cm-handle already exists"
+ }
+ ]
+ failedUpdatedCmHandles:
+ type: array
+ items:
+ $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+ example: [
+ {
+ "cmHandle": "my-cm-handle-02",
+ "errorCode": "02",
+ "errorText": "cm-handle does not exist"
+ }
+ ]
+ failedRemovedCmHandles:
+ type: array
+ items:
+ $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+ example: [
+ {
+ "cmHandle": "my-cm-handle-02",
+ "errorCode": "02",
+ "errorText": "cm-handle does not exist"
+ }
+ ]
+ CmHandlerRegistrationErrorResponse:
+ type: object
+ properties:
+ cmHandle:
+ type: string
+ example: my-cm-handle
+ errorCode:
+ type: string
+ example: '01'
+ errorText:
+ type: string
+ example: 'cm-handle already exists'
RestInputCmHandle:
required:
diff --git a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml
index 3cd8e8baf..5e61d0962 100755
--- a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml
+++ b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml
@@ -31,7 +31,7 @@ updateDmiRegistration:
schema:
$ref: 'components.yaml#/components/schemas/RestDmiPluginRegistration'
responses:
- 204:
+ 200:
$ref: 'components.yaml#/components/responses/NoContent'
400:
$ref: 'components.yaml#/components/responses/BadRequest'
@@ -40,4 +40,7 @@ updateDmiRegistration:
403:
$ref: 'components.yaml#/components/responses/Forbidden'
500:
- $ref: 'components.yaml#/components/responses/InternalServerError'
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yaml#/components/schemas/DmiPluginRegistrationErrorResponse'
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
index c9d26f2a5..e9a69b305 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021 Bell Canada
+ * Copyright (C) 2021-2022 Bell Canada
* Modifications Copyright (C) 2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,10 +21,17 @@
package org.onap.cps.ncmp.rest.controller;
+import java.util.List;
+import java.util.stream.Collectors;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status;
+import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
import org.onap.cps.ncmp.rest.api.NetworkCmProxyInventoryApi;
+import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse;
+import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse;
import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -41,14 +48,57 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
/**
* Update DMI Plugin Registration (used for first registration also).
+ *
* @param restDmiPluginRegistration the registration data
*/
@Override
- public ResponseEntity<Void> updateDmiPluginRegistration(
+ public ResponseEntity updateDmiPluginRegistration(
final @Valid RestDmiPluginRegistration restDmiPluginRegistration) {
- networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
- ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration));
- return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ final DmiPluginRegistrationResponse dmiPluginRegistrationResponse =
+ networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+ ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration));
+ final DmiPluginRegistrationErrorResponse failedRegistrationErrorResponse =
+ getFailureRegistrationResponse(dmiPluginRegistrationResponse);
+ return allRegistrationsSuccessful(failedRegistrationErrorResponse)
+ ? new ResponseEntity<>(HttpStatus.OK)
+ : new ResponseEntity<>(failedRegistrationErrorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ private boolean allRegistrationsSuccessful(
+ final DmiPluginRegistrationErrorResponse dmiPluginRegistrationErrorResponse) {
+ return dmiPluginRegistrationErrorResponse.getFailedCreatedCmHandles().isEmpty()
+ && dmiPluginRegistrationErrorResponse.getFailedUpdatedCmHandles().isEmpty()
+ && dmiPluginRegistrationErrorResponse.getFailedRemovedCmHandles().isEmpty();
+
+ }
+
+ private DmiPluginRegistrationErrorResponse getFailureRegistrationResponse(
+ final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+ final var dmiPluginRegistrationErrorResponse = new DmiPluginRegistrationErrorResponse();
+ dmiPluginRegistrationErrorResponse.setFailedCreatedCmHandles(
+ getFailedResponses(dmiPluginRegistrationResponse.getCreatedCmHandles()));
+ dmiPluginRegistrationErrorResponse.setFailedUpdatedCmHandles(
+ getFailedResponses(dmiPluginRegistrationResponse.getUpdatedCmHandles()));
+ dmiPluginRegistrationErrorResponse.setFailedRemovedCmHandles(
+ getFailedResponses(dmiPluginRegistrationResponse.getRemovedCmHandles()));
+
+ return dmiPluginRegistrationErrorResponse;
+ }
+
+ private List<CmHandlerRegistrationErrorResponse> getFailedResponses(
+ final List<CmHandleRegistrationResponse> cmHandleRegistrationResponseList) {
+ return cmHandleRegistrationResponseList.stream()
+ .filter(cmHandleRegistrationResponse -> cmHandleRegistrationResponse.getStatus() == Status.FAILURE)
+ .map(this::toCmHandleRegistrationErrorResponse)
+ .collect(Collectors.toList());
+ }
+
+ private CmHandlerRegistrationErrorResponse toCmHandleRegistrationErrorResponse(
+ final CmHandleRegistrationResponse registrationResponse) {
+ return new CmHandlerRegistrationErrorResponse()
+ .cmHandle(registrationResponse.getCmHandle())
+ .errorCode(registrationResponse.getRegistrationError().errorCode)
+ .errorText(registrationResponse.getErrorText());
}
}
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
index 9b1c2e87c..30b6beb37 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021 Bell Canada
+ * Copyright (C) 2021-2022 Bell Canada
* Modifications Copyright (C) 2021-2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,11 @@ package org.onap.cps.ncmp.rest.controller
import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.TestUtils
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse
+import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse
+import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse
import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration
import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
@@ -36,6 +40,9 @@ import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
@WebMvcTest(NetworkCmProxyInventoryController)
@@ -58,7 +65,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
@Value('${rest.api.ncmp-inventory-base-path}/v1')
def ncmpBasePathV1
- def 'Dmi plugin registration #scenario' () {
+ def 'Dmi plugin registration #scenario'() {
given: 'a dmi plugin registration with #scenario'
def jsonData = TestUtils.getResourceFileContent(dmiRegistrationJson)
and: 'the expected rest input as an object'
@@ -72,9 +79,9 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
.content(jsonData)
).andReturn().response
then: 'the converted object is forwarded to the registration service'
- 1 * mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration)
+ 1 * mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse()
and: 'response status is no content'
- response.status == HttpStatus.NO_CONTENT.value()
+ response.status == HttpStatus.OK.value()
where: 'the following registration json is used'
scenario | dmiRegistrationJson
'multiple services, added, updated and removed cm handles and many properties' | 'dmi_registration_all_singing_and_dancing.json'
@@ -82,7 +89,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
'without any properties' | 'dmi_registration_without_properties.json'
}
- def 'Dmi plugin registration with invalid json' () {
+ def 'Dmi plugin registration with invalid json'() {
given: 'a dmi plugin registration with #scenario'
def jsonDataWithUndefinedDataLabel = '{"notAdmiPlugin":""}'
when: 'post request is performed & registration is called with correct DMI plugin information'
@@ -95,4 +102,74 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
response.status == HttpStatus.BAD_REQUEST.value()
}
+ def 'DMI Registration: All cm-handles operations processed successfully.'() {
+ given: 'a dmi plugin registration'
+ def dmiRegistrationRequest = '{}'
+ and: 'service can register cm-handles successfully'
+ def dmiRegistrationResponse = new DmiPluginRegistrationResponse(
+ createdCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1')],
+ updatedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-2')],
+ removedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-3')]
+ )
+ mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
+ when: 'registration endpoint is invoked'
+ def response = mvc.perform(
+ post("$ncmpBasePathV1/ch")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(dmiRegistrationRequest)
+ ).andReturn().response
+ then: 'response status is ok'
+ response.status == HttpStatus.OK.value()
+ and: 'the response body is empty'
+ response.getContentAsString() == ''
+
+ }
+
+ def 'DMI Registration Error Handling: #scenario.'() {
+ given: 'a dmi plugin registration'
+ def dmiRegistrationRequest = '{}'
+ and: '#scenario: service failed to register few cm-handle'
+ def dmiRegistrationResponse = new DmiPluginRegistrationResponse(
+ createdCmHandles: [createCmHandleResponse],
+ updatedCmHandles: [updateCmHandleResponse],
+ removedCmHandles: [removeCmHandleResponse]
+ )
+ mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
+ when: 'registration endpoint is invoked'
+ def response = mvc.perform(
+ post("$ncmpBasePathV1/ch")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(dmiRegistrationRequest)
+ ).andReturn().response
+ then: 'request status is internal server error'
+ response.status == HttpStatus.INTERNAL_SERVER_ERROR.value()
+ and: 'the response body is in the expected format'
+ def responseBody = jsonObjectMapper.convertJsonString(response.getContentAsString(), DmiPluginRegistrationErrorResponse)
+ and: 'contains only the failure responses'
+ responseBody.getFailedCreatedCmHandles() == expectedFailedCreatedCmHandle
+ responseBody.getFailedUpdatedCmHandles() == expectedFailedUpdateCmHandle
+ responseBody.getFailedRemovedCmHandles() == expectedFailedRemovedCmHandle
+ where:
+ scenario | createCmHandleResponse | updateCmHandleResponse | removeCmHandleResponse || expectedFailedCreatedCmHandle | expectedFailedUpdateCmHandle | expectedFailedRemovedCmHandle
+ 'only create failed' | failedResponse('cm-handle-1') | successResponse('cm-handle-2') | successResponse('cm-handle-3') || [failedRestResponse('cm-handle-1')] | [] | []
+ 'only update failed' | successResponse('cm-handle-1') | failedResponse('cm-handle-2') | successResponse('cm-handle-3') || [] | [failedRestResponse('cm-handle-2')] | []
+ 'only delete failed' | successResponse('cm-handle-1') | successResponse('cm-handle-2') | failedResponse('cm-handle-3') || [] | [] | [failedRestResponse('cm-handle-3')]
+ 'all three failed' | failedResponse('cm-handle-1') | failedResponse('cm-handle-2') | failedResponse('cm-handle-3') || [failedRestResponse('cm-handle-1')] | [failedRestResponse('cm-handle-2')] | [failedRestResponse('cm-handle-3')]
+ 'create update failed' | failedResponse('cm-handle-1') | failedResponse('cm-handle-2') | successResponse('cm-handle-3') || [failedRestResponse('cm-handle-1')] | [failedRestResponse('cm-handle-2')] | []
+ 'create delete failed' | failedResponse('cm-handle-1') | successResponse('cm-handle-2') | failedResponse('cm-handle-3') || [failedRestResponse('cm-handle-1')] | [] | [failedRestResponse('cm-handle-3')]
+ 'update delete failed' | successResponse('cm-handle-1') | failedResponse('cm-handle-2') | failedResponse('cm-handle-3') || [] | [failedRestResponse('cm-handle-2')] | [failedRestResponse('cm-handle-3')]
+ }
+
+ def failedRestResponse(cmHandle) {
+ return new CmHandlerRegistrationErrorResponse('cmHandle': cmHandle, 'errorCode': '00', 'errorText': 'Failed')
+ }
+
+ def failedResponse(cmHandle) {
+ return CmHandleRegistrationResponse.createFailureResponse(cmHandle, new RuntimeException("Failed"))
+ }
+
+ def successResponse(cmHandle) {
+ return CmHandleRegistrationResponse.createSuccessResponse(cmHandle)
+ }
+
}
diff --git a/cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json b/cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json
index fd8b56b02..c2a307db2 100644
--- a/cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json
+++ b/cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json
@@ -3,7 +3,7 @@
"dmiModelPlugin":"service3",
"createdCmHandles":[
{
- "cmHandle":"ch1(new)",
+ "cmHandle":"ch1-new",
"cmHandleProperties":{
"dmiProp1":"ch1-dmi1",
"dmiProp2":"ch1-dmi2"
@@ -14,7 +14,7 @@
}
},
{
- "cmHandle":"ch2(new)",
+ "cmHandle":"ch2-new",
"cmHandleProperties":{
"dmiProp1":"ch2-dmi1",
"dmiProp2":"ch2-dmi2"
@@ -27,7 +27,7 @@
],
"updatedCmHandles":[
{
- "cmHandle":"ch3(upd)",
+ "cmHandle":"ch3-upd",
"cmHandleProperties":{
"dmiProp1":"ch3-dmi1"
},
diff --git a/cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json b/cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json
index 58a1a9836..26acdbdcb 100644
--- a/cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json
+++ b/cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json
@@ -2,7 +2,7 @@
"dmiPlugin": "service1",
"updatedCmHandles":[
{
- "cmHandle":"ch3(upd)",
+ "cmHandle":"ch3-upd",
"cmHandleProperties":{
"dmiProp1":"ch3-dmi1",
"dmiProp2":null
diff --git a/cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json b/cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json
index 395c098d2..a5dd7b0aa 100644
--- a/cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json
+++ b/cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json
@@ -4,7 +4,7 @@
"dmiModelPlugin":"service3",
"createdCmHandles":[
{
- "cmHandle": "ch1(new)"
+ "cmHandle": "ch1-new"
}
]
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index 69e9c7ba1..1a69e45d3 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -31,7 +31,6 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMES
import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
-import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -54,16 +53,17 @@ import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandlesList;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.CpsValidator;
import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -101,22 +101,16 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final DmiPluginRegistration dmiPluginRegistration) {
dmiPluginRegistration.validateDmiPluginRegistration();
final var dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
- try {
- dmiPluginRegistrationResponse.setRemovedCmHandles(
- parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles()));
- if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
- parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration);
- }
- if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
- dmiPluginRegistrationResponse.setUpdatedCmHandles(
- networkCmProxyDataServicePropertyHandler
- .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
- }
- } catch (final JsonProcessingException | DataNodeNotFoundException e) {
- final String errorMessage = String.format(
- "Error occurred while processing the CM-handle registration request, caused by : [%s]",
- e.getMessage());
- throw new DataValidationException(errorMessage, e.getMessage(), e);
+ dmiPluginRegistrationResponse.setRemovedCmHandles(
+ parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles()));
+ if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
+ dmiPluginRegistrationResponse.setCreatedCmHandles(
+ parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration));
+ }
+ if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
+ dmiPluginRegistrationResponse.setUpdatedCmHandles(
+ networkCmProxyDataServicePropertyHandler
+ .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
}
return dmiPluginRegistrationResponse;
}
@@ -127,7 +121,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final String acceptParamInHeader,
final String optionsParamInQuery,
final String topicParamInQuery) {
-
+ CpsValidator.validateNameCharacters(cmHandleId);
return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery);
}
@@ -138,6 +132,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final String acceptParamInHeader,
final String optionsParamInQuery,
final String topicParamInQuery) {
+ CpsValidator.validateNameCharacters(cmHandleId);
return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery);
}
@@ -148,6 +143,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final OperationEnum operation,
final String requestData,
final String dataType) {
+ CpsValidator.validateNameCharacters(cmHandleId);
return handleResponse(
dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation,
requestData, dataType), operation);
@@ -156,6 +152,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
@Override
public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
+ CpsValidator.validateNameCharacters(cmHandleId);
return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
}
@@ -178,6 +175,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
*/
@Override
public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) {
+ CpsValidator.validateNameCharacters(cmHandleId);
final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
final YangModelCmHandle yangModelCmHandle =
yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
@@ -214,14 +212,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
* THis method registers a cm handle and initiates modules sync.
*
* @param dmiPluginRegistration dmi plugin registration information.
- * @throws JsonProcessingException thrown if json is malformed or missing.
+ * @return cm-handle registration response for create cm-handle requests.
*/
- public void parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
- final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException {
- final YangModelCmHandlesList createdYangModelCmHandlesList =
- getUpdatedYangModelCmHandlesList(dmiPluginRegistration,
- dmiPluginRegistration.getCreatedCmHandles());
- registerAndSyncNewCmHandles(createdYangModelCmHandlesList);
+ public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
+ final DmiPluginRegistration dmiPluginRegistration) {
+ return dmiPluginRegistration.getCreatedCmHandles().stream()
+ .map(cmHandle ->
+ YangModelCmHandle.toYangModelCmHandle(
+ dmiPluginRegistration.getDmiPlugin(),
+ dmiPluginRegistration.getDmiDataPlugin(),
+ dmiPluginRegistration.getDmiModelPlugin(), cmHandle)
+ )
+ .map(this::registerAndSyncNewCmHandle)
+ .collect(Collectors.toList());
}
private static Object handleResponse(final ResponseEntity<?> responseEntity, final OperationEnum operation) {
@@ -234,23 +237,23 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
}
- private YangModelCmHandlesList getUpdatedYangModelCmHandlesList(
- final DmiPluginRegistration dmiPluginRegistration,
- final List<NcmpServiceCmHandle> updatedCmHandles) {
- return YangModelCmHandlesList.toYangModelCmHandlesList(
- dmiPluginRegistration.getDmiPlugin(),
- dmiPluginRegistration.getDmiDataPlugin(),
- dmiPluginRegistration.getDmiModelPlugin(),
- updatedCmHandles);
- }
-
- private void registerAndSyncNewCmHandles(final YangModelCmHandlesList yangModelCmHandlesList) {
- final String cmHandleJsonData = jsonObjectMapper.asJsonString(yangModelCmHandlesList);
- cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+ private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) {
+ try {
+ CpsValidator.validateNameCharacters(yangModelCmHandle.getId());
+ final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
+ jsonObjectMapper.asJsonString(yangModelCmHandle));
+ cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
cmHandleJsonData, NO_TIMESTAMP);
-
- for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandlesList.getYangModelCmHandles()) {
syncModulesAndCreateAnchor(yangModelCmHandle);
+ return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId());
+ } catch (final AlreadyDefinedException alreadyDefinedException) {
+ return CmHandleRegistrationResponse.createFailureResponse(
+ yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST);
+ } catch (final DataValidationException dataValidationException) {
+ return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(),
+ RegistrationError.CM_HANDLE_INVALID_ID);
+ } catch (final Exception exception) {
+ return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception);
}
}
@@ -259,12 +262,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
createAnchor(yangModelCmHandle);
}
- private List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration(
+ protected List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration(
final List<String> tobeRemovedCmHandles) {
final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
new ArrayList<>(tobeRemovedCmHandles.size());
for (final String cmHandle : tobeRemovedCmHandles) {
try {
+ CpsValidator.validateNameCharacters(cmHandle);
deleteSchemaSetWithCascade(cmHandle);
cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
"/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP);
@@ -274,8 +278,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
cmHandle, dataNodeNotFoundException.getMessage());
cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
.createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+ } catch (final DataValidationException dataValidationException) {
+ log.error("Unable to de-register cm-handle id: {}, caused by: {}",
+ cmHandle, dataValidationException.getMessage());
+ cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
+ .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_INVALID_ID));
} catch (final Exception exception) {
- log.error("Unable to de-register cm-handleIdd : {} , caused by : {}",
+ log.error("Unable to de-register cm-handle id : {} , caused by : {}",
cmHandle, exception.getMessage());
cmHandleRegistrationResponses.add(
CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
index c838a752e..ff79f8724 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
@@ -45,8 +45,10 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.utils.CpsValidator;
import org.springframework.stereotype.Service;
@Slf4j
@@ -72,6 +74,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
final String cmHandle = ncmpServiceCmHandle.getCmHandleID();
try {
+ CpsValidator.validateNameCharacters(cmHandle);
final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandle);
final DataNode existingCmHandleDataNode =
cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXpath,
@@ -83,8 +86,14 @@ public class NetworkCmProxyDataServicePropertyHandler {
cmHandle, e.getMessage());
cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
.createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+ } catch (final DataValidationException e) {
+ log.error("Unable to update cm handle : {}, caused by : {}",
+ cmHandle, e.getMessage());
+ cmHandleRegistrationResponses.add(
+ CmHandleRegistrationResponse.createFailureResponse(cmHandle,
+ RegistrationError.CM_HANDLE_INVALID_ID));
} catch (final Exception exception) {
- log.error("Unable to update dataNode for cmHandleId : {} , caused by : {}",
+ log.error("Unable to update cmHandle : {} , caused by : {}",
cmHandle, exception.getMessage());
cmHandleRegistrationResponses.add(
CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
index 47062b354..e46b9e3da 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
@@ -21,6 +21,8 @@
package org.onap.cps.ncmp.api.impl.yangmodels;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Strings;
import java.util.ArrayList;
@@ -41,6 +43,7 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
@Getter
@Setter
@NoArgsConstructor
+@JsonInclude(Include.NON_NULL)
public class YangModelCmHandle {
private String id;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java
deleted file mode 100644
index 261a0181c..000000000
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 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.ncmp.api.impl.yangmodels;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import lombok.Getter;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-
-@Getter
-public class YangModelCmHandlesList {
-
- @JsonProperty("cm-handles")
- private final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>();
-
- /**
- * Create a YangModelCmHandleList given all service names and a collection of cmHandles.
- * @param dmiServiceName the dmi service name
- * @param dmiDataServiceName the dmi data service name
- * @param dmiModelServiceName the dmi model service name
- * @param ncmpServiceCmHandles cm handles rest model
- * @return instance of YangModelCmHandleList
- */
- public static YangModelCmHandlesList toYangModelCmHandlesList(final String dmiServiceName,
- final String dmiDataServiceName,
- final String dmiModelServiceName,
- final Collection<NcmpServiceCmHandle>
- ncmpServiceCmHandles) {
- final YangModelCmHandlesList yangModelCmHandlesList = new YangModelCmHandlesList();
- for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
- final YangModelCmHandle yangModelCmHandle =
- YangModelCmHandle.toYangModelCmHandle(
- dmiServiceName,
- dmiDataServiceName,
- dmiModelServiceName,
- ncmpServiceCmHandle);
- yangModelCmHandlesList.add(yangModelCmHandle);
- }
- return yangModelCmHandlesList;
- }
-
- /**
- * Add a yangModelCmHandle.
- *
- * @param yangModelCmHandle the yangModelCmHandle to add
- */
- public void add(final YangModelCmHandle yangModelCmHandle) {
- yangModelCmHandles.add(yangModelCmHandle);
- }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
index e183ed114..1da2aa943 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,7 +78,8 @@ public class CmHandleRegistrationResponse {
public enum RegistrationError {
UNKNOWN_ERROR("00", "Unknown error"),
CM_HANDLE_ALREADY_EXIST("01", "cm-handle already exists"),
- CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist");
+ CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist"),
+ CM_HANDLE_INVALID_ID("03", "cm-handle has an invalid character(s) in id");
public final String errorCode;
public final String errorText;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java
index ce2f3e66a..8a3d26414 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java
@@ -20,6 +20,7 @@
package org.onap.cps.ncmp.api.models;
+import java.util.Collections;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -27,7 +28,7 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class DmiPluginRegistrationResponse {
- private List<CmHandleRegistrationResponse> createdCmHandles;
- private List<CmHandleRegistrationResponse> updatedCmHandles;
- private List<CmHandleRegistrationResponse> removedCmHandles;
+ private List<CmHandleRegistrationResponse> createdCmHandles = Collections.emptyList();
+ private List<CmHandleRegistrationResponse> updatedCmHandles = Collections.emptyList();
+ private List<CmHandleRegistrationResponse> removedCmHandles = Collections.emptyList();
} \ No newline at end of file
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
index 23d24384c..cb4d5ef40 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
@@ -21,9 +21,8 @@
package org.onap.cps.ncmp.api.impl
-import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
-import java.util.function.Predicate
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
@@ -34,6 +33,7 @@ import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
import org.onap.cps.ncmp.api.models.DmiPluginRegistration
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
@@ -42,7 +42,10 @@ import spock.lang.Shared
import spock.lang.Specification
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
@@ -75,73 +78,190 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
// Spock validated invocation order between multiple then blocks
then: 'cm-handles are removed first'
- 1 * mockCpsDataService.deleteListOrListElement(*_)
+ 1 * objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_)
then: 'cm-handles are created'
- 1 * mockCpsDataService.saveListElements(*_)
+ 1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_)
then: 'cm-handles are updated'
1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_)
}
- def 'Register or re-register a DMI Plugin for the given cm-handle(s) with #scenario process.'() {
- given: 'a registration'
- def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
- ncmpServiceCmHandle.cmHandleID = '123'
- ncmpServiceCmHandle.dmiProperties = [dmiProp1: 'dmiValue1', dmiProp2: 'dmiValue2']
- ncmpServiceCmHandle.publicProperties = [publicProp1: 'publicValue1', publicProp2: 'publicValue2']
- dmiPluginRegistration.createdCmHandles = createdCmHandles
- dmiPluginRegistration.updatedCmHandles = updatedCmHandles
- dmiPluginRegistration.removedCmHandles = removedCmHandles
- def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,' +
- '"additional-properties":[{"name":"dmiProp1","value":"dmiValue1"},{"name":"dmiProp2","value":"dmiValue2"}],' +
- '"public-properties":[{"name":"publicProp1","value":"publicValue1"},{"name":"publicProp2","value":"publicValue2"}]' +
- '}]}'
- when: 'registration is updated and modules are synced'
+ def 'DMI Registration: Response from all operations types are in response'() {
+ given: 'a registration with operations of all three types'
+ def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+ dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+ dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+ dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
+ and: 'update cm-handles can be processed successfully'
+ def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')]
+ mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> updateResponses
+ and: 'create cm-handles can be processed successfully'
+ def createdResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-1')]
+ objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) >> createdResponses
+ and: 'delete cm-handles can be processed successfully'
+ def removeResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-3')]
+ objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) >> removeResponses
+ when: 'registration is processed'
+ def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
+ then: 'response has values from all operations'
+ response.getRemovedCmHandles() == removeResponses
+ response.getCreatedCmHandles() == createdResponses
+ response.getUpdatedCmHandles() == updateResponses
+
+
+ }
+
+ def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
+ given: 'a registration '
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+ dmiDataPlugin: dmiDataPlugin)
+ dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
+ when: 'update registration and sync module is called with correct DMI plugin information'
objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'save list elements is invoked with the expected parameters'
- expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
- '/dmi-registry', expectedJsonData, noTimestamp)
- and: 'update data node leaves is called with correct parameters'
- expectedCallsToUpdateCmHandleProperty * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(updatedCmHandles)
- and: 'delete schema set is invoked with the correct parameters'
- expectedCallsToDeleteSchemaSetAndListElement * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'cmHandle001', CASCADE_DELETE_ALLOWED)
- and: 'delete list or list element is invoked with the correct parameters'
- expectedCallsToDeleteSchemaSetAndListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
- 'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
+ then: 'create cm handles registration and sync modules is called with the correct plugin information'
+ 1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
where:
- scenario | createdCmHandles | updatedCmHandles | removedCmHandles || expectedCallsToSaveNode | expectedCallsToDeleteSchemaSetAndListElement | expectedCallsToUpdateCmHandleProperty
- 'create' | [ncmpServiceCmHandle] | [] | [] || 1 | 0 | 0
- 'update' | [] | [ncmpServiceCmHandle] | [] || 0 | 0 | 1
- 'delete' | [] | [] | cmHandlesArray || 0 | 1 | 0
- 'create, update and delete' | [ncmpServiceCmHandle] | [ncmpServiceCmHandle] | cmHandlesArray || 1 | 1 | 1
- 'no valid data' | [] | [] | [] || 0 | 0 | 0
+ scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin
+ 'combined DMI plugin' | 'service1' | '' | ''
+ 'data & model DMI plugins' | '' | 'service1' | 'service2'
+ 'data & model using same service' | '' | 'service1' | 'service1'
}
- def 'Create CM-Handle: Register a DMI Plugin for the given cm-handle(s) without DMI properties.'() {
+ def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() {
+ given: 'a registration '
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+ dmiDataPlugin: dmiDataPlugin)
+ dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
+ when: 'registration is called with incorrect DMI plugin information'
+ objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'a DMI Request Exception is thrown with correct message details'
+ def exceptionThrown = thrown(DmiRequestException.class)
+ assert exceptionThrown.getMessage().contains(expectedMessageDetails)
+ and: 'registration is not called'
+ 0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
+ where:
+ scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
+ 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names'
+ 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names'
+ 'null DMI plugins' | null | null | null || 'No DMI plugin service names'
+ 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names'
+ '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names'
+ '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names'
+ 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name'
+ 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name'
+ }
+
+ def 'Create CM-Handle Successfully: #scenario.'() {
given: 'a registration without cm-handle properties'
- NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
- ncmpServiceCmHandle.cmHandleID = '123'
- ncmpServiceCmHandle.dmiProperties = Collections.emptyMap()
- ncmpServiceCmHandle.publicProperties = Collections.emptyMap()
- dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
- def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[],"public-properties":[]}]}'
+ dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)]
when: 'registration is updated'
- objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'save list elements is invoked with the expected parameters'
- 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
- '/dmi-registry', expectedJsonData, noTimestamp)
+ def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'a successful response is received'
+ response.getCreatedCmHandles().size() == 1
+ with(response.getCreatedCmHandles().get(0)) {
+ assert it.status == Status.SUCCESS
+ assert it.cmHandle == 'cmhandle'
+ }
+ and: 'save list elements is invoked with the expected parameters'
+ interaction {
+ def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}"""
+ 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
+ '/dmi-registry', expectedJsonData, noTimestamp)
+ }
+ then: 'model sync is invoked with expected parameters'
+ 1 * objectUnderTest.syncModulesAndCreateAnchor(_) >> { YangModelCmHandle yangModelCmHandle ->
+ {
+ assert yangModelCmHandle.id == 'cmhandle'
+ assert yangModelCmHandle.dmiServiceName == 'my-server'
+ assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getPublicProperties()) == expectedPublicProperties
+ assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getDmiProperties()) == expectedDmiProperties
+
+ }
+ }
+ where:
+ scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties
+ 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]'
+ 'with only public properties' | [:] | ['public-key': 'public-value'] || '[]' | '[{"name":"public-key","value":"public-value"}]'
+ 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]'
+ 'without dmi & public properties' | [:] | [:] || '[]' | '[]'
+
}
- def 'Create CM-Handle: Register a DMI Plugin for a given cm-handle(s) with JSON processing errors during process.'() {
- given: 'a registration without cm-handle properties '
- def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'some-plugin')
- dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
- and: 'an json processing exception occurs'
- spiedJsonObjectMapper.asJsonString(_) >> { throw (new JsonProcessingException('')) }
- when: 'registration is updated and modules are synced'
- objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'a data validation exception is thrown'
- thrown(DataValidationException)
+ def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() {
+ given: 'a registration with three cm-handles to be created'
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+ createdCmHandles: [new NcmpServiceCmHandle(cmHandleID: 'cmhandle1'),
+ new NcmpServiceCmHandle(cmHandleID: 'cmhandle2'),
+ new NcmpServiceCmHandle(cmHandleID: 'cmhandle3')])
+ and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
+ mockCpsDataService.saveListElements(_, _, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {}
+ when: 'registration is updated to create cm-handles'
+ def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'a response is received for all cm-handles'
+ response.getCreatedCmHandles().size() == 3
+ and: '1st and 3rd cm-handle are created successfully'
+ with(response.getCreatedCmHandles().get(0)) {
+ assert it.status == Status.SUCCESS
+ assert it.cmHandle == 'cmhandle1'
+ }
+ with(response.getCreatedCmHandles().get(2)) {
+ assert it.status == Status.SUCCESS
+ assert it.cmHandle == 'cmhandle3'
+ }
+ and: '2nd cm-handle creation fails'
+ with(response.getCreatedCmHandles().get(1)) {
+ assert it.status == Status.FAILURE
+ assert it.registrationError == UNKNOWN_ERROR
+ assert it.errorText == 'Failed'
+ assert it.cmHandle == 'cmhandle2'
+ }
+ }
+
+ def 'Create CM-Handle Error Handling: Registration fails: #scenario'() {
+ given: 'a registration without cm-handle properties'
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+ dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: cmHandleId)]
+ and: 'cm-handler registration fails: #scenario'
+ mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception }
+ when: 'registration is updated'
+ def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'a failure response is received'
+ response.getCreatedCmHandles().size() == 1
+ with(response.getCreatedCmHandles().get(0)) {
+ assert it.status == Status.FAILURE
+ assert it.cmHandle == cmHandleId
+ assert it.registrationError == expectedError
+ assert it.errorText == expectedErrorText
+ }
+ and: 'model-sync is not invoked'
+ 0 * objectUnderTest.syncModulesAndCreateAnchor(_)
+ where:
+ scenario | cmHandleId | exception || expectedError | expectedErrorText
+ 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+ 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id'
+ 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
+ }
+
+ def 'Create CM-Handle Error Handling: Model Sync fails'() {
+ given: 'objects under test without disabled model sync'
+ def objectUnderTest = getObjectUnderTest()
+ and: 'a registration without cm-handle properties'
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+ dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+ and: 'cm-handler models sync fails'
+ objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') }
+ when: 'registration is updated'
+ def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'a failure response is received'
+ response.getCreatedCmHandles().size() == 1
+ with(response.getCreatedCmHandles().get(0)) {
+ assert it.status == Status.FAILURE
+ assert it.cmHandle == 'cmhandle'
+ assert it.registrationError == UNKNOWN_ERROR
+ assert it.errorText == 'Model-Sync failed'
+ }
+ and: 'cm-handle is registered'
+ 1 * mockCpsDataService.saveListElements(*_)
}
def 'Update CM-Handle: Update Operation Response is added to the response'() {
@@ -151,12 +271,13 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
and: 'cm-handle updates can be processed successfully'
def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'),
CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")),
- CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST)]
+ CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST),
+ CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)]
mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse
when: 'registration is updated'
def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
then: 'the response contains updateOperationResponse'
- assert response.getUpdatedCmHandles().size() == 3
+ assert response.getUpdatedCmHandles().size() == 4
assert response.getUpdatedCmHandles().containsAll(updateOperationResponse)
}
@@ -174,7 +295,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
and: 'successful response is received'
assert response.getRemovedCmHandles().size() == 1
with(response.getRemovedCmHandles().get(0)) {
- assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+ assert it.status == Status.SUCCESS
assert it.cmHandle == 'cmhandle'
}
where:
@@ -195,16 +316,19 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
response.getRemovedCmHandles().size() == 3
and: '1st and 3rd cm-handle deletes successfully'
with(response.getRemovedCmHandles().get(0)) {
- assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+ assert it.status == Status.SUCCESS
+ assert it.cmHandle == 'cmhandle1'
}
with(response.getRemovedCmHandles().get(2)) {
- assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+ assert it.status == Status.SUCCESS
+ assert it.cmHandle == 'cmhandle3'
}
- and: '2nd cmhandle deletion fails'
+ and: '2nd cm-handle deletion fails'
with(response.getRemovedCmHandles().get(1)) {
- assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+ assert it.status == Status.FAILURE
assert it.registrationError == UNKNOWN_ERROR
assert it.errorText == 'Failed'
+ assert it.cmHandle == 'cmhandle2'
}
}
@@ -223,7 +347,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
and: 'a failure response is received'
assert response.getRemovedCmHandles().size() == 1
with(response.getRemovedCmHandles().get(0)) {
- assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+ assert it.status == Status.FAILURE
assert it.cmHandle == 'cmhandle'
assert it.errorText == 'Failed'
assert it.registrationError == UNKNOWN_ERROR
@@ -243,61 +367,26 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
and: 'a failure response is received'
assert response.getRemovedCmHandles().size() == 1
with(response.getRemovedCmHandles().get(0)) {
- assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+ assert it.status == Status.FAILURE
assert it.cmHandle == 'cmhandle'
assert it.registrationError == expectedError
assert it.errorText == expectedErrorText
}
where:
- scenario | deleteListElementException | expectedError | expectedErrorText
- 'cm-handle does not exist' | new DataNodeNotFoundException("", "", "") | CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
- 'an unexpected exception' | new RuntimeException("Failed") | UNKNOWN_ERROR | 'Failed'
- }
-
- def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
- given: 'a registration '
- def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
- dmiDataPlugin: dmiDataPlugin)
- dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
- when: 'update registration and sync module is called with correct DMI plugin information'
- objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'create cm handles registration and sync modules is called with the correct plugin information'
- 1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
- where:
- scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin
- 'combined DMI plugin' | 'service1' | '' | ''
- 'data & model DMI plugins' | '' | 'service1' | 'service2'
- 'data & model using same service' | '' | 'service1' | 'service1'
- }
-
- def 'Create CM-handle Error Handling: Invalid DMI plugin service name with #scenario'() {
- given: 'a registration '
- def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
- dmiDataPlugin: dmiDataPlugin)
- dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
- when: 'registration is called with incorrect DMI plugin information'
- objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'a DMI Request Exception is thrown with correct message details'
- def exceptionThrown = thrown(DmiRequestException.class)
- assert exceptionThrown.getMessage().contains(expectedMessageDetails)
- and: 'registration is not called'
- 0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
- where:
- scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
- 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names'
- 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names'
- 'null DMI plugins' | null | null | null || 'No DMI plugin service names'
- 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names'
- '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names'
- '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names'
- 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name'
- 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name'
+ scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText
+ 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException("", "", "") || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+ 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id'
+ 'an unexpected exception' | 'cmhandle' | new RuntimeException("Failed") || UNKNOWN_ERROR | 'Failed'
}
def getObjectUnderTestWithModelSyncDisabled() {
- def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
- mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever))
+ def objectUnderTest = getObjectUnderTest()
objectUnderTest.syncModulesAndCreateAnchor(*_) >> null
return objectUnderTest
}
+
+ def getObjectUnderTest() {
+ return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
+ mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever))
+ }
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
index 06b2032b9..bf5bb73a9 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
@@ -289,9 +289,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def 'Getting Yang Resources.'() {
when: 'yang resources is called'
- objectUnderTest.getYangResourcesModuleReferences('some cm handle')
+ objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
then: 'CPS module services is invoked for the correct dataspace and cm handle'
- 1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some cm handle')
+ 1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cm-handle')
}
def 'Get cm handle identifiers for the given module names.'() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
index f6264f492..7aacbda51 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
@@ -21,7 +21,10 @@
package org.onap.cps.ncmp.api.impl
+import org.onap.cps.spi.exceptions.DataValidationException
+
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
@@ -118,7 +121,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
'no original properties' | [] || 0
}
- def 'Exception thrown when we try to update cmHandle'() {
+ def '#scenario error leads to #exception when we try to update cmHandle'() {
given: 'cm handles request'
def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: [:], dmiProperties: [:])]
and: 'data node cannot be found'
@@ -135,9 +138,10 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
assert it.errorText == expectedErrorText
}
where:
- scenario | exception || expectedError | expectedErrorText
- 'cmhandle does not exist' | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
- 'unexpected error' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
+ scenario | cmHandleId | exception || expectedError | expectedErrorText
+ 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+ 'Unknown' | 'cmHandleId' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
+ 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id'
}
def 'Multiple update operations in a single request'() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
index c5ef2f446..4476998d8 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
@@ -26,8 +26,8 @@ import spock.lang.Specification
class CmHandleRegistrationResponseSpec extends Specification {
- def 'Successful CmHandle Registration Response'() {
- when: 'CMHandle response is created'
+ def 'Successful cm-handle Registration Response'() {
+ when: 'cm-handle response is created'
def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle')
then: 'a success response is returned'
with(cmHandleRegistrationResponse) {
@@ -39,8 +39,8 @@ class CmHandleRegistrationResponseSpec extends Specification {
cmHandleRegistrationResponse.errorText == null
}
- def 'Failed Cm Handle Registration Response: for unexpected exception'() {
- when: 'CMHandle response is created for an unexpected exception'
+ def 'Failed cm-handle Registration Response: for unexpected exception'() {
+ when: 'cm-handle response is created for an unexpected exception'
def cmHandleRegistrationResponse =
CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error'))
then: 'the response is created with expected value'
@@ -51,18 +51,21 @@ class CmHandleRegistrationResponseSpec extends Specification {
}
}
- def 'Failed Cm Handle Registration Response: for known error'() {
- when: 'CMHandle response is created for known error'
+ def 'Failed cm-handle Registration Response: for #scenario'() {
+ when: 'cm-handle failure response is created for #scenario'
def cmHandleRegistrationResponse =
- CmHandleRegistrationResponse.createFailureResponse('cmHandle', RegistrationError.CM_HANDLE_ALREADY_EXIST)
+ CmHandleRegistrationResponse.createFailureResponse(cmHandleId, registrationError)
then: 'the response is created with expected value'
with(cmHandleRegistrationResponse) {
- assert it.registrationError == RegistrationError.CM_HANDLE_ALREADY_EXIST
- assert it.cmHandle == 'cmHandle'
+ assert it.registrationError == registrationError
+ assert it.cmHandle == cmHandleId
assert it.status == Status.FAILURE
- assert errorText == RegistrationError.CM_HANDLE_ALREADY_EXIST.errorText
+ assert errorText == registrationError.errorText
}
-
+ where:
+ scenario | cmHandleId | registrationError
+ 'cm-handle already exists' | 'cmHandle' | RegistrationError.CM_HANDLE_ALREADY_EXIST
+ 'cm-handle id is invalid' | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID
}
}
diff --git a/cps-rest/docs/openapi/cpsAdmin.yml b/cps-rest/docs/openapi/cpsAdmin.yml
index a25f81eaf..5852c0cf1 100644
--- a/cps-rest/docs/openapi/cpsAdmin.yml
+++ b/cps-rest/docs/openapi/cpsAdmin.yml
@@ -29,6 +29,8 @@ dataspaces:
responses:
'201':
$ref: 'components.yml#/components/responses/Created'
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
'401':
$ref: 'components.yml#/components/responses/Unauthorized'
'403':
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
index 58a5ebf04..41ad9ca5b 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
@@ -22,14 +22,13 @@
package org.onap.cps.rest.controller
-import org.mapstruct.factory.Mappers
-
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.mapstruct.factory.Mappers
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.spi.exceptions.AlreadyDefinedException
@@ -73,7 +72,7 @@ class AdminRestControllerSpec extends Specification {
def 'Create new dataspace.'() {
given: 'an endpoint'
- def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+ def createDataspaceEndpoint = "$basePath/v1/dataspaces"
when: 'post is invoked'
def response =
mvc.perform(
@@ -88,7 +87,7 @@ class AdminRestControllerSpec extends Specification {
def 'Create dataspace over existing with same name.'() {
given: 'an endpoint'
- def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+ def createDataspaceEndpoint = "$basePath/v1/dataspaces"
and: 'the service method throws an exception indicating the dataspace is already defined'
def thrownException = new AlreadyDefinedException(dataspaceName, new RuntimeException())
mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException }
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
index 44f7f7715..2cb01ac1e 100755
--- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation
+ * Copyright (C) 2020-2021 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
index ecc9bf098..79d6e03d4 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
@@ -23,7 +23,6 @@ package org.onap.cps.api;
import java.util.Collection;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.NonNull;
import org.onap.cps.spi.CascadeDeleteAllowed;
import org.onap.cps.spi.exceptions.DataInUseException;
import org.onap.cps.spi.model.ModuleReference;
@@ -42,8 +41,8 @@ public interface CpsModuleService {
* @param yangResourcesNameToContentMap yang resources (files) as a mep where key is resource name
* and value is content
*/
- void createSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName,
- @NonNull Map<String, String> yangResourcesNameToContentMap);
+ void createSchemaSet(String dataspaceName, String schemaSetName,
+ Map<String, String> yangResourcesNameToContentMap);
/**
* Create a schema set from new modules and existing modules.
@@ -52,8 +51,8 @@ public interface CpsModuleService {
* @param newModuleNameToContentMap YANG resources map where key is a module name and value is content
* @param moduleReferences List of YANG resources module references of the modules
*/
- void createSchemaSetFromModules(@NonNull String dataspaceName, @NonNull String schemaSetName,
- @NonNull Map<String, String> newModuleNameToContentMap,
+ void createSchemaSetFromModules(String dataspaceName, String schemaSetName,
+ Map<String, String> newModuleNameToContentMap,
Collection<ModuleReference> moduleReferences);
/**
@@ -63,7 +62,7 @@ public interface CpsModuleService {
* @param schemaSetName schema set name
* @return a SchemaSet
*/
- SchemaSet getSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName);
+ SchemaSet getSchemaSet(String dataspaceName, String schemaSetName);
/**
* Deletes Schema Set.
@@ -74,8 +73,8 @@ public interface CpsModuleService {
* @throws DataInUseException if cascadeDeleteAllowed is set to CASCADE_DELETE_PROHIBITED and there
* is associated anchor record exists in database
*/
- void deleteSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName,
- @NonNull CascadeDeleteAllowed cascadeDeleteAllowed);
+ void deleteSchemaSet(String dataspaceName, String schemaSetName,
+ CascadeDeleteAllowed cascadeDeleteAllowed);
/**
* Retrieve module references for the given dataspace name.
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
index beb0a1540..68ae1ebf0 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
package org.onap.cps.api;
import java.util.Collection;
-import org.checkerframework.checker.nullness.qual.NonNull;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
@@ -40,7 +39,7 @@ public interface CpsQueryService {
* included in the output
* @return a collection of data nodes
*/
- Collection<DataNode> queryDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
- @NonNull String cpsPath, @NonNull FetchDescendantsOption fetchDescendantsOption);
+ Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
+ String cpsPath, FetchDescendantsOption fetchDescendantsOption);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
index 1013addbe..7bec1e39f 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
@@ -30,6 +30,7 @@ import org.onap.cps.api.CpsAdminService;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.spi.CpsAdminPersistenceService;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.utils.CpsValidator;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -43,42 +44,50 @@ public class CpsAdminServiceImpl implements CpsAdminService {
@Override
public void createDataspace(final String dataspaceName) {
+ CpsValidator.validateNameCharacters(dataspaceName);
cpsAdminPersistenceService.createDataspace(dataspaceName);
}
@Override
public void deleteDataspace(final String dataspaceName) {
+ CpsValidator.validateNameCharacters(dataspaceName);
cpsAdminPersistenceService.deleteDataspace(dataspaceName);
}
@Override
public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName, anchorName);
cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
}
@Override
public Collection<Anchor> getAnchors(final String dataspaceName) {
+ CpsValidator.validateNameCharacters(dataspaceName);
return cpsAdminPersistenceService.getAnchors(dataspaceName);
}
@Override
public Collection<Anchor> getAnchors(final String dataspaceName, final String schemaSetName) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetName);
}
@Override
public Anchor getAnchor(final String dataspaceName, final String anchorName) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
return cpsAdminPersistenceService.getAnchor(dataspaceName, anchorName);
}
@Override
public void deleteAnchor(final String dataspaceName, final String anchorName) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
cpsDataService.deleteDataNodes(dataspaceName, anchorName, OffsetDateTime.now());
cpsAdminPersistenceService.deleteAnchor(dataspaceName, anchorName);
}
@Override
public Collection<String> queryAnchorNames(final String dataspaceName, final Collection<String> moduleNames) {
+ CpsValidator.validateNameCharacters(dataspaceName);
final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
}
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
index 643614f4f..399457dd6 100755
--- 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
@@ -36,6 +36,7 @@ import org.onap.cps.spi.exceptions.DataValidationException;
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.CpsValidator;
import org.onap.cps.utils.YangUtils;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -56,6 +57,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String jsonData,
final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final var dataNode = buildDataNode(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, ROOT_NODE_XPATH, Operation.CREATE);
@@ -64,6 +66,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.CREATE);
@@ -72,6 +75,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveListElements(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<DataNode> listElementDataNodeCollection =
buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
@@ -82,12 +86,14 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
final FetchDescendantsOption fetchDescendantsOption) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
return cpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption);
}
@Override
public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService
.updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
@@ -102,6 +108,7 @@ public class CpsDataServiceImpl implements CpsDataService {
final Collection<DataNode> dataNodeUpdates =
buildDataNodes(dataspaceName, anchorName,
parentNodeXpath, dataNodeUpdatesAsJson);
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
}
@@ -122,6 +129,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
@@ -130,6 +138,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<DataNode> newListElements =
buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
@@ -138,6 +147,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
}
@@ -145,6 +155,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, dataNodeXpath, Operation.DELETE);
}
@@ -152,6 +163,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void deleteDataNodes(final String dataspaceName, final String anchorName,
final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
@@ -160,6 +172,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
final OffsetDateTime observedTimestamp) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, listNodeXpath, Operation.DELETE);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
index f0e79c60c..8e43227f9 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
@@ -33,6 +33,7 @@ import org.onap.cps.spi.exceptions.SchemaSetInUseException;
import org.onap.cps.spi.model.Anchor;
import org.onap.cps.spi.model.ModuleReference;
import org.onap.cps.spi.model.SchemaSet;
+import org.onap.cps.utils.CpsValidator;
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -48,6 +49,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
@Override
public void createSchemaSet(final String dataspaceName, final String schemaSetName,
final Map<String, String> yangResourcesNameToContentMap) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
final var yangTextSchemaSourceSet
= YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap);
cpsModulePersistenceService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap);
@@ -58,6 +60,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
public void createSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
final Map<String, String> newModuleNameToContentMap,
final Collection<ModuleReference> moduleReferences) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
cpsModulePersistenceService.storeSchemaSetFromModules(dataspaceName, schemaSetName,
newModuleNameToContentMap, moduleReferences);
@@ -65,6 +68,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
@Override
public SchemaSet getSchemaSet(final String dataspaceName, final String schemaSetName) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
final var yangTextSchemaSourceSet = yangTextSchemaSourceSetCache
.get(dataspaceName, schemaSetName);
return SchemaSet.builder().name(schemaSetName).dataspaceName(dataspaceName)
@@ -75,6 +79,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
@Transactional
public void deleteSchemaSet(final String dataspaceName, final String schemaSetName,
final CascadeDeleteAllowed cascadeDeleteAllowed) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName);
if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) {
throw new SchemaSetInUseException(dataspaceName, schemaSetName);
@@ -89,12 +94,14 @@ public class CpsModuleServiceImpl implements CpsModuleService {
@Override
public Collection<ModuleReference> getYangResourceModuleReferences(final String dataspaceName) {
+ CpsValidator.validateNameCharacters(dataspaceName);
return cpsModulePersistenceService.getYangResourceModuleReferences(dataspaceName);
}
@Override
public Collection<ModuleReference> getYangResourcesModuleReferences(final String dataspaceName,
final String anchorName) {
+ CpsValidator.validateNameCharacters(dataspaceName);
return cpsModulePersistenceService.getYangResourceModuleReferences(dataspaceName, anchorName);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
index dd9f160db..c2003d6bf 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
@@ -25,6 +25,7 @@ import org.onap.cps.api.CpsQueryService;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.utils.CpsValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -37,6 +38,7 @@ public class CpsQueryServiceImpl implements CpsQueryService {
@Override
public Collection<DataNode> queryDataNodes(final String dataspaceName, final String anchorName,
final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) {
+ CpsValidator.validateNameCharacters(dataspaceName, anchorName);
return cpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
}
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
index 03b52a308..fb881a97b 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
@@ -24,6 +24,7 @@ package org.onap.cps.api.impl;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Map;
import org.onap.cps.spi.CpsModulePersistenceService;
+import org.onap.cps.utils.CpsValidator;
import org.onap.cps.yang.YangTextSchemaSourceSet;
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
import org.springframework.beans.factory.annotation.Autowired;
@@ -52,6 +53,7 @@ public class YangTextSchemaSourceSetCache {
*/
@Cacheable(key = "#p0.concat('-').concat(#p1)")
public YangTextSchemaSourceSet get(final String dataspaceName, final String schemaSetName) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
final Map<String, String> yangResourceNameToContent =
cpsModulePersistenceService.getYangSchemaResources(dataspaceName, schemaSetName);
return YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent);
@@ -69,6 +71,7 @@ public class YangTextSchemaSourceSetCache {
@CanIgnoreReturnValue
public YangTextSchemaSourceSet updateCache(final String dataspaceName, final String schemaSetName,
final YangTextSchemaSourceSet yangTextSchemaSourceSet) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
return yangTextSchemaSourceSet;
}
@@ -81,6 +84,7 @@ public class YangTextSchemaSourceSetCache {
*/
@CacheEvict(key = "#p0.concat('-').concat(#p1)")
public void removeFromCache(final String dataspaceName, final String schemaSetName) {
+ CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
// Spring provides implementation for removing object from cache
}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
new file mode 100644
index 000000000..dd1649563
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 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.utils;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.exceptions.DataValidationException;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class CpsValidator {
+
+ private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray();
+
+ /**
+ * Validate characters in names within cps.
+ * @param names names of data to be validated
+ */
+ public static void validateNameCharacters(final String... names) {
+ for (final String name : names) {
+ final Collection<Character> charactersOfName = Lists.charactersOf(name);
+ for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) {
+ if (charactersOfName.contains(unsupportedCharacter)) {
+ throw new DataValidationException("Name or ID Validation Error.",
+ name + " invalid token encountered at position " + (name.indexOf(unsupportedCharacter) + 1));
+ }
+ }
+ }
+ }
+}
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
index eb06199d1..fc1293cb7 100644
--- 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
@@ -50,9 +50,9 @@ class CpsDataServiceImplSpec extends Specification {
mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
}
- def dataspaceName = 'some dataspace'
- def anchorName = 'some anchor'
- def schemaSetName = 'some schema set'
+ def dataspaceName = 'some-dataspace'
+ def anchorName = 'some-anchor'
+ def schemaSetName = 'some-schema-set'
def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
def observedTimestamp = OffsetDateTime.now()
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
index 4878f4c11..aa01b4401 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
@@ -35,8 +35,8 @@ class CpsQueryServiceImplSpec extends Specification {
def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
given: 'a dataspace name, an anchor name and a cps path'
- def dataspaceName = 'some dataspace'
- def anchorName = 'some anchor'
+ def dataspaceName = 'some-dataspace'
+ def anchorName = 'some-anchor'
def cpsPath = '/cps-path'
when: 'queryDataNodes is invoked'
objectUnderTest.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption)
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
new file mode 100644
index 000000000..191472cee
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 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.utils
+
+import org.onap.cps.spi.exceptions.DataValidationException
+import spock.lang.Specification
+
+class CpsValidatorSpec extends Specification {
+
+
+ def 'Validating a valid string.'() {
+ when: 'the string is validated using a valid name'
+ CpsValidator.validateNameCharacters('name-with-no-spaces')
+ then: 'no exception is thrown'
+ noExceptionThrown()
+ }
+
+ def 'Validating an invalid string.'() {
+ when: 'the string is validated using an invalid name'
+ CpsValidator.validateNameCharacters(name)
+ then: 'a data validation exception is thrown'
+ def exceptionThrown = thrown(DataValidationException)
+ and: 'the error was encountered at the following index in #scenario'
+ assert exceptionThrown.getDetails().contains(expectedErrorMessage)
+ where: 'the following names are used'
+ scenario | name || expectedErrorMessage
+ 'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5'
+ 'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9'
+ }
+}
diff --git a/csit/tests/cps-model-sync/cps-model-sync.robot b/csit/tests/cps-model-sync/cps-model-sync.robot
index dfad94861..7de1f3a1b 100644
--- a/csit/tests/cps-model-sync/cps-model-sync.robot
+++ b/csit/tests/cps-model-sync/cps-model-sync.robot
@@ -42,7 +42,7 @@ Register data node and sync modules.
${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch
${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonDataCreate}
- Should Be Equal As Strings ${response.status_code} 204
+ Should Be Equal As Strings ${response.status_code} 200
Get CM Handle details and confirm it has been registered.
${uri}= Set Variable ${ncmpBasePath}/v1/ch/PNFDemo
@@ -61,7 +61,7 @@ Update data node and sync modules.
${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch
${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonDataUpdate}
- Should Be Equal As Strings ${response.status_code} 204
+ Should Be Equal As Strings ${response.status_code} 200
Get CM Handle details and confirm it has been updated.
${uri}= Set Variable ${ncmpBasePath}/v1/ch/PNFDemo
diff --git a/docs/cps-path.rst b/docs/cps-path.rst
index bc46681d1..e8a75d9cf 100644
--- a/docs/cps-path.rst
+++ b/docs/cps-path.rst
@@ -1,6 +1,6 @@
.. This work is licensed under a Creative Commons Attribution 4.0 International License.
.. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021 Nordix Foundation
+.. Copyright (C) 2021-2022 Nordix Foundation
.. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
.. _design:
@@ -20,17 +20,137 @@ The CPS path parameter is used for querying xpaths. CPS path is inspired by the
This section describes the functionality currently supported by CPS Path.
-Sample Data
-===========
+Sample Yang Model
+=================
-The xml below describes some basic data to be used to illustrate the CPS Path functionality.
+.. code-block::
+
+ module stores {
+ yang-version 1.1;
+ namespace "org:onap:ccsdk:sample";
+
+ prefix book-store;
+
+ revision "2020-09-15" {
+ description
+ "Sample Model";
+ }
+ container shops {
+
+ container bookstore {
+
+ leaf bookstore-name {
+ type string;
+ }
+
+ leaf name {
+ type string;
+ }
+
+ list categories {
+
+ key "code";
+
+ leaf code {
+ type uint16;
+ }
+
+ leaf name {
+ type string;
+ }
+
+ leaf numberOfBooks {
+ type uint16;
+ }
+
+ container books {
+
+ list book {
+ key title;
+
+ leaf title {
+ type string;
+ }
+ leaf price {
+ type uint16;
+ }
+ leaf-list label {
+ type string;
+ }
+ leaf-list edition {
+ type string;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+**Note.** 'categories' is a Yang List and 'code' is its key leaf. All other data nodes are Yang Containers. 'label' and 'edition' are both leaf-lists.
+
+**Note.** CPS accepts only json data. The xml data presented here is for illustration purposes only.
+
+The json and xml below describes some basic data to be used to illustrate the CPS Path functionality.
+
+Sample Data in Json
+===================
+
+.. code-block:: json
+
+ {
+ "shops": {
+ "bookstore": {
+ "bookstore-name": "Chapters",
+ "name": "Chapters",
+ "categories": [
+ {
+ "code": 1,
+ "name": "SciFi",
+ "numberOfBooks": 2,
+ "books": {
+ "book": [
+ {
+ "title": "2001: A Space Odyssey",
+ "price": 5,
+ "label": ["sale", "classic"],
+ "edition": ["1968", "2018"]
+ },
+ {
+ "title": "Dune",
+ "price": 5,
+ "label": ["classic"],
+ "edition": ["1965"]
+ }
+ ]
+ }
+ },
+ {
+ "code": 2,
+ "name": "Kids",
+ "numberOfBooks": 1,
+ "books": {
+ "book": [
+ {
+ "title": "Matilda"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+
+Sample Data in XML
+==================
.. code-block:: xml
<shops>
<bookstore name="Chapters">
<bookstore-name>Chapters</bookstore-name>
- <categories code="1" name="SciFi" numberOfBooks="2">
+ <categories code=1 name="SciFi" numberOfBooks="2">
<books>
<book title="2001: A Space Odyssey" price="5">
<label>sale</label>
@@ -44,7 +164,7 @@ The xml below describes some basic data to be used to illustrate the CPS Path fu
</book>
</books>
</categories>
- <categories code="2" name="Kids" numberOfBooks="1">
+ <categories code=2 name="Kids" numberOfBooks="1">
<books>
<book title="Matilda" />
</books>
@@ -52,8 +172,6 @@ The xml below describes some basic data to be used to illustrate the CPS Path fu
</bookstore>
</shops>
-**Note.** 'categories' is a Yang List and 'code' is its key leaf. All other data nodes are Yang Containers. 'label' and 'edition' are both leaf-lists.
-
General Notes
=============
@@ -79,12 +197,14 @@ absolute-path
**Examples**
- ``/shops/bookstore``
- - ``/shops/bookstore/categories[@code=1]``
- - ``/shops/bookstore/categories[@code=1]/book``
+ - ``/shops/bookstore/categories[@code='1']/books``
+ - ``/shops/bookstore/categories[@code='1']/books/book[@title='2001: A Space Odyssey']``
**Limitations**
- Absolute paths must start with the top element (data node) as per the model tree.
- Each list reference must include a valid instance reference to the key for that list. Except when it is the last element.
+ - The Absolute path to list with integer key will not work. It needs to be surrounded with a single quote ([@code='1'])
+ as if it is a string. This will be fixed in `CPS-961 <https://jira.onap.org/browse/CPS-961>`_
descendant-path
---------------
@@ -95,7 +215,7 @@ descendant-path
**Examples**
- ``//bookstore``
- - ``//categories[@code=1]/book``
+ - ``//categories[@code='1']/books``
- ``//bookstore/categories``
**Limitations**
@@ -113,7 +233,7 @@ leaf-conditions
- ``/shops/bookstore/categories[@numberOfBooks=1]``
- ``//categories[@name="Kids"]``
- ``//categories[@name='Kids']``
- - ``//categories[@code=1]/books/book[@title='Dune' and @price=5]``
+ - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]``
**Limitations**
- Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s).
@@ -156,9 +276,9 @@ The ancestor axis can be added to any CPS path query but has to be the last part
**Examples**
- ``//book/ancestor::categories``
- - ``//categories[@genre="SciFi"]/book/ancestor::bookstore``
- - ``book/ancestor::categories[@code=1]/books``
- - ``//book/label[text()="classic"]/ancestor::shop``
+ - ``//categories[@code='2']/books/ancestor::bookstore``
+ - ``//book/ancestor::categories[@code='1']/books``
+ - ``//book/label[text()="classic"]/ancestor::shops``
**Limitations**
- Ancestor list elements can only be addressed using the list key leaf.
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index c2e2a5fdc..2fea4a21f 100755
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -73,6 +73,9 @@ Null can no longer be passed within the dmi plugin service names when registerin
`CPS-837 <https://jira.onap.org/browse/CPS-837>`_ null is now used to indicate if a property should be removed as part
of cm handle registration.
+The Absolute path to list with integer key will not work. Please refer `CPS-961 <https://jira.onap.org/browse/CPS-961>`_
+for more information.
+
*Known Vulnerabilities*
None