diff options
209 files changed, 6889 insertions, 2895 deletions
diff --git a/.github/workflows/gerrit-verify.yaml b/.github/workflows/gerrit-verify.yaml deleted file mode 100644 index 91849215af..0000000000 --- a/.github/workflows/gerrit-verify.yaml +++ /dev/null @@ -1,151 +0,0 @@ ---- -name: Call Gerrit Verify - -# yamllint disable-line rule:truthy -on: - workflow_dispatch: - inputs: - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - secrets: - GERRIT_SSH_PRIVKEY: - description: "SSH Key for the authorized user account" - required: true - -concurrency: - # yamllint disable-line rule:line-length - group: gerrit-verify-${{ github.workflow }}-${{ github.event.inputs.GERRIT_BRANCH}}-${{ github.event.inputs.GERRIT_CHANGE_ID || github.run_id }} - cancel-in-progress: true - -jobs: - prepare: - runs-on: ubuntu-latest - steps: - - name: Clear votes - # yamllint disable-line rule:line-length - uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_USER }} - key: ${{ secrets.GERRIT_SSH_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: clear - comment-only: true - - name: Allow replication - run: sleep 10s - - actionlint: - needs: prepare - runs-on: ubuntu-latest - steps: - - name: Gerrit Checkout - # yamllint disable-line rule:line-length - uses: lfit/checkout-gerrit-change-action@54d751e8bd167bc91f7d665dabe33fae87aaaa63 # v0.9 - with: - gerrit-refspec: ${{ inputs.GERRIT_REFSPEC }} - gerrit-project: ${{ inputs.GERRIT_PROJECT }} - gerrit-url: ${{ vars.GERRIT_URL }} - delay: "0s" - - name: Download actionlint - id: get_actionlint - # yamllint disable-line rule:line-length - run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - shell: bash - - name: Check workflow files - run: ${{ steps.get_actionlint.outputs.executable }} -color - shell: bash - - # run pre-commit tox env separately to get use of more parallel processing - pre-commit: - needs: prepare - runs-on: ubuntu-latest - steps: - - name: Gerrit Checkout - # yamllint disable-line rule:line-length - uses: lfit/checkout-gerrit-change-action@54d751e8bd167bc91f7d665dabe33fae87aaaa63 # v0.9 - with: - gerrit-refspec: ${{ inputs.GERRIT_REFSPEC }} - gerrit-project: ${{ inputs.GERRIT_PROJECT }} - gerrit-url: ${{ vars.GERRIT_URL }} - delay: "0s" - # yamllint disable-line rule:line-length - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 - with: - python-version: "3.11" - - name: Run static analysis and format checkers - run: pipx run pre-commit run --all-files --show-diff-on-failure - - checkov-scan: - needs: prepare - runs-on: ubuntu-latest - steps: - - name: Gerrit Checkout - # yamllint disable-line rule:line-length - uses: lfit/checkout-gerrit-change-action@54d751e8bd167bc91f7d665dabe33fae87aaaa63 # v0.9 - with: - gerrit-refspec: ${{ inputs.GERRIT_REFSPEC }} - gerrit-project: ${{ inputs.GERRIT_PROJECT }} - gerrit-url: ${{ vars.GERRIT_URL }} - delay: "0s" - submodules: "true" - - name: Checkov GitHub Action - uses: bridgecrewio/checkov-action@v12 - with: - output_format: cli,sarif - output_file_path: console,results.sarif - - vote: - if: ${{ always() }} - needs: [prepare, actionlint, pre-commit, checkov-scan] - runs-on: ubuntu-latest - steps: - - name: Get conclusion - uses: im-open/workflow-conclusion@e4f7c4980600fbe0818173e30931d3550801b992 # v2.2.3 - - name: Set vote - # yamllint disable-line rule:line-length - uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_USER }} - key: ${{ secrets.GERRIT_SSH_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: ${{ env.WORKFLOW_CONCLUSION }} - comment-only: true diff --git a/.gitignore b/.gitignore index 5f77b3b0a6..a721cb4489 100755 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.war *.log *.log.zip +*.*~ cps-ncmp-rest-stub/dependency-reduced-pom.xml cps-application/archunit_store @@ -21,9 +21,9 @@ # Configuration Persistence Service ## General Information -* [CPS Project Wiki](https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project) +* [CPS Project Wiki](https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16398157/Configuration+Persistence+Service+Project) ## For Developers -* [Developer Wiki](https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Developer%27s+Landing+Page) +* [Developer Wiki](https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16442177/Configuration+Persistence+Service+Developer+s+Landing+Page) * [Building and running CPS locally](docker-compose/README.md) diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index f618836d0d..ca5068f0da 100644 --- a/checkstyle/pom.xml +++ b/checkstyle/pom.xml @@ -26,7 +26,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>checkstyle</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <profiles> <profile> @@ -54,7 +54,7 @@ </profiles> <properties> - <nexusproxy>https://nexus.onap.org</nexusproxy> + <onap.nexus.url>https://nexus.onap.org</onap.nexus.url> <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> <sonar.skip>true</sonar.skip> <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> @@ -66,7 +66,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.8.2</version> + <version>3.1.2</version> </plugin> </plugins> </pluginManagement> @@ -106,12 +106,12 @@ <repository> <id>ecomp-releases</id> <name>ECOMP Release Repository</name> - <url>${nexusproxy}${releaseNexusPath}</url> + <url>${onap.nexus.url}${releaseNexusPath}</url> </repository> <snapshotRepository> <id>ecomp-snapshots</id> <name>ECOMP Snapshot Repository</name> - <url>${nexusproxy}${snapshotNexusPath}</url> + <url>${onap.nexus.url}${snapshotNexusPath}</url> </snapshotRepository> </distributionManagement> </project>
\ No newline at end of file diff --git a/checkstyle/src/main/resources/apache-license-2.regexp.txt b/checkstyle/src/main/resources/apache-license-2.regexp.txt new file mode 100644 index 0000000000..80f7be3fd8 --- /dev/null +++ b/checkstyle/src/main/resources/apache-license-2.regexp.txt @@ -0,0 +1,15 @@ +^/[*]+$ +^ \* Copyright .*$ +^ \*( )?$ +^ \* 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. +^ [*]+/$
\ No newline at end of file diff --git a/checkstyle/src/main/resources/cps-checkstyle/check-license.xml b/checkstyle/src/main/resources/cps-checkstyle/check-license.xml new file mode 100644 index 0000000000..a19dbe98e8 --- /dev/null +++ b/checkstyle/src/main/resources/cps-checkstyle/check-license.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<!-- +# ============LICENSE_START======================================================= +# Copyright (C) 2024 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========================================================= +--> +<!DOCTYPE module PUBLIC + "-//Puppy Crawl//DTD Check Configuration 1.3//EN" + "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> +<!-- Checks the license headers expected by ONAP. --> +<module name="Checker"> + <property name="charset" value="UTF-8"/> + <property name="severity" value="error"/> + <module name="RegexpSingleline"> + <property name="format" value="under the Apache License, Version 2\.0"/> + <property name="minimum" value="1"/> + <property name="maximum" value="10"/> + </module> + <module name="RegexpSingleline"> + <property name="format" value="http://www.apache\.org/licenses/LICENSE-2\.0"/> + <property name="minimum" value="1"/> + <property name="maximum" value="10"/> + </module> +</module>
\ No newline at end of file diff --git a/checkstyle/src/main/resources/cps-checkstyle/cps-java-style.xml b/checkstyle/src/main/resources/cps-checkstyle/cps-java-style.xml new file mode 100644 index 0000000000..6e1664a4a3 --- /dev/null +++ b/checkstyle/src/main/resources/cps-checkstyle/cps-java-style.xml @@ -0,0 +1,342 @@ +<?xml version="1.0"?> +<!-- + Copyright (C) 2024 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. +--> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> + +<!-- + Checkstyle configuration that checks the Google coding conventions from Google Java Style + that can be found at https://google.github.io/styleguide/javaguide.html + + Checkstyle is very configurable. Be sure to read the documentation at + http://checkstyle.org (or in your downloaded distribution). + + To completely disable a check, just comment it out or delete it from the file. + To suppress certain violations please review suppression filters. + + Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov. + --> + +<!-- +To update the checkstyle version in ONAP see: +https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16429749/How+to+update+ONAP+checkstyle+when+Checkstyle+steps+their+version + --> + +<module name="Checker"> + <property name="charset" value="UTF-8" /> + + <property name="severity" value="warning" /> + + <property name="fileExtensions" value="java, properties, xml" /> + <!-- Excludes all 'module-info.java' files --> + <!-- See https://checkstyle.org/config_filefilters.html --> + <module name="BeforeExecutionExclusionFileFilter"> + <property name="fileNamePattern" value="module\-info\.java$" /> + </module> + <!-- https://checkstyle.org/config_filters.html#SuppressionFilter --> + <module name="SuppressionFilter"> + <property name="file" value="${org.checkstyle.google.suppressionfilter.config}" + default="checkstyle-suppressions.xml" /> + <property name="optional" value="true" /> + </module> + <module name="SuppressWarningsFilter"/> + + <!-- Checks for whitespace --> + <!-- See http://checkstyle.org/config_whitespace.html --> + <module name="FileTabCharacter"> + <property name="eachLine" value="true" /> + </module> + + <module name="LineLength"> + <property name="fileExtensions" value="java" /> + <property name="max" value="120" /> + <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://" /> + </module> + + <module name="TreeWalker"> + <module name="OuterTypeFilename" /> + <module name="IllegalTokenText"> + <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL" /> + <property name="format" + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)" /> + <property name="message" + value="Consider using special escape sequence instead of octal value or Unicode escaped value." /> + </module> + <module name="AvoidEscapedUnicodeCharacters"> + <property name="allowEscapesForControlCharacters" value="true" /> + <property name="allowByTailComment" value="true" /> + <property name="allowNonPrintableEscapes" value="true" /> + </module> + <module name="AvoidStarImport" /> + <module name="OneTopLevelClass" /> + <module name="NoLineWrap"> + <property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT" /> + </module> + <module name="EmptyBlock"> + <property name="option" value="TEXT" /> + <property name="tokens" + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH" /> + </module> + <module name="NeedBraces"> + <property name="tokens" value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE" /> + </module> + <module name="LeftCurly"> + <property name="tokens" + value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF, + INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT, + LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF, + OBJBLOCK, STATIC_INIT" /> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlySame" /> + <property name="tokens" + value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, + LITERAL_DO" /> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlyAlone" /> + <property name="option" value="alone" /> + <property name="tokens" + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF" /> + </module> + <module name="SuppressionXpathSingleFilter"> + <!-- suppression is required till https://github.com/checkstyle/checkstyle/issues/7541 --> + <property name="id" value="RightCurlyAlone" /> + <property name="query" + value="//RCURLY[parent::SLIST[count(./*)=1] + or preceding-sibling::*[last()][self::LCURLY]]" /> + </module> + <module name="WhitespaceAfter"> + <property name="tokens" + value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, + LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE" /> + </module> + <module name="WhitespaceAround"> + <property name="allowEmptyConstructors" value="true" /> + <property name="allowEmptyLambdas" value="true" /> + <property name="allowEmptyMethods" value="true" /> + <property name="allowEmptyTypes" value="true" /> + <property name="allowEmptyLoops" value="true" /> + <property name="tokens" + value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, + BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND, + LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, + LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, + LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, + NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, + SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND" /> + <message key="ws.notFollowed" + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)" /> + <message key="ws.notPreceded" value="WhitespaceAround: ''{0}'' is not preceded with whitespace." /> + </module> + <module name="OneStatementPerLine" /> + <module name="MultipleVariableDeclarations" /> + <module name="ArrayTypeStyle" /> + <module name="MissingSwitchDefault" /> + <module name="FallThrough" /> + <module name="UpperEll" /> + <module name="ModifierOrder" /> + <module name="EmptyLineSeparator"> + <property name="tokens" + value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF" /> + <property name="allowNoEmptyLineBetweenFields" value="true" /> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapDot" /> + <property name="tokens" value="DOT" /> + <property name="option" value="nl" /> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapComma" /> + <property name="tokens" value="COMMA" /> + <property name="option" value="EOL" /> + </module> + <module name="SeparatorWrap"> + <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 --> + <property name="id" value="SeparatorWrapEllipsis" /> + <property name="tokens" value="ELLIPSIS" /> + <property name="option" value="EOL" /> + </module> + <module name="SeparatorWrap"> + <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 --> + <property name="id" value="SeparatorWrapArrayDeclarator" /> + <property name="tokens" value="ARRAY_DECLARATOR" /> + <property name="option" value="EOL" /> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapMethodRef" /> + <property name="tokens" value="METHOD_REF" /> + <property name="option" value="nl" /> + </module> + <module name="PackageName"> + <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$" /> + <message key="name.invalidPattern" value="Package name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="TypeName"> + <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF" /> + <message key="name.invalidPattern" value="Type name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="MemberName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$" /> + <message key="name.invalidPattern" value="Member name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="ParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$" /> + <message key="name.invalidPattern" value="Parameter name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="LambdaParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$" /> + <message key="name.invalidPattern" value="Lambda parameter name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="CatchParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$" /> + <message key="name.invalidPattern" value="Catch parameter name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="LocalVariableName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$" /> + <message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="ClassTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)" /> + <message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="MethodTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)" /> + <message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="InterfaceTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)" /> + <message key="name.invalidPattern" value="Interface type name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="NoFinalizer" /> + <module name="GenericWhitespace"> + <message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace." /> + <message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace." /> + <message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace." /> + <message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace." /> + </module> + <module name="Indentation"> + <property name="basicOffset" value="4" /> + <property name="braceAdjustment" value="0" /> + <property name="caseIndent" value="4" /> + <property name="throwsIndent" value="4" /> + <property name="lineWrappingIndentation" value="4" /> + <property name="arrayInitIndent" value="4" /> + </module> + <module name="AbbreviationAsWordInName"> + <property name="ignoreFinal" value="false" /> + <property name="allowedAbbreviationLength" value="1" /> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF, + PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF" /> + </module> + <module name="OverloadMethodsDeclarationOrder" /> + <module name="VariableDeclarationUsageDistance" /> + <module name="CustomImportOrder"> + <property name="sortImportsInGroupAlphabetically" value="true" /> + <property name="separateLineBetweenGroups" value="true" /> + <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE" /> + <property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF" /> + </module> + <module name="MethodParamPad"> + <property name="tokens" + value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF, + SUPER_CTOR_CALL, ENUM_CONSTANT_DEF" /> + </module> + <module name="NoWhitespaceBefore"> + <property name="tokens" + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, + LABELED_STAT, METHOD_REF" /> + <property name="allowLineBreaks" value="true" /> + </module> + <module name="ParenPad"> + <property name="tokens" + value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF, + EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, + METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA" /> + </module> + <module name="OperatorWrap"> + <property name="option" value="NL" /> + <property name="tokens" + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF " /> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationMostCases" /> + <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF" /> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationVariables" /> + <property name="tokens" value="VARIABLE_DEF" /> + <property name="allowSamelineMultipleAnnotations" value="true" /> + </module> + <module name="NonEmptyAtclauseDescription" /> + <module name="InvalidJavadocPosition" /> + <module name="JavadocTagContinuationIndentation" /> + <module name="SummaryJavadoc"> + <property name="forbiddenSummaryFragments" + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )" /> + </module> + <module name="JavadocParagraph" /> + <module name="AtclauseOrder"> + <property name="tagOrder" value="@param, @return, @throws, @deprecated" /> + <property name="target" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF" /> + </module> + <module name="JavadocMethod"> + <property name="allowMissingParamTags" value="true" /> + <property name="allowMissingReturnTag" value="true" /> + <property name="allowedAnnotations" value="Override, Test" /> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF" /> + </module> + <module name="MissingJavadocMethod"> + <property name="minLineCount" value="2" /> + <property name="allowedAnnotations" value="Override, Test" /> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF" /> + <property name="ignoreMethodNamesRegex" value="^(test|before|after)[a-zA-Z0-9_]*$" /> + </module> + <module name="MethodName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$" /> + <message key="name.invalidPattern" value="Method name ''{0}'' must match pattern ''{1}''." /> + </module> + <module name="SingleLineJavadoc"> + <property name="ignoreInlineTags" value="false" /> + </module> + <module name="EmptyCatchBlock"> + <property name="exceptionVariableName" value="expected" /> + </module> + <module name="CommentsIndentation"> + <property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN" /> + </module> + <!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter --> + <module name="SuppressionXpathFilter"> + <property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}" + default="checkstyle-xpath-suppressions.xml" /> + <property name="optional" value="true" /> + </module> + <module name="SuppressWarningsHolder"/> + <module name="FinalLocalVariable"> + <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/> + <property name="validateEnhancedForLoopVariable" value="true"/> + </module> + <module name="UnusedImports"/> + </module> +</module>
\ No newline at end of file diff --git a/checkstyle/src/main/resources/cps-java-style.xml b/checkstyle/src/main/resources/cps-java-style.xml deleted file mode 100644 index 67b2863695..0000000000 --- a/checkstyle/src/main/resources/cps-java-style.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0"?> -<!-- -============LICENSE_START======================================================= - Copyright (C) 2020 Pantheon.tech - ================================================================================ - 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========================================================= ---> -<!DOCTYPE module PUBLIC - "-//Puppy Crawl//DTD Check Configuration 1.3//EN" - "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> -<module name="Checker"> - <property name="charset" value="UTF-8"/> - <property name="severity" value="warning"/> - <property name="fileExtensions" value="java, properties, xml"/> - - <module name="TreeWalker"> - <module name="FinalLocalVariable"> - <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/> - <property name="validateEnhancedForLoopVariable" value="true"/> - </module> - <module name="UnusedImports"/> - </module> -</module>
\ No newline at end of file diff --git a/cps-application/pom.xml b/cps-application/pom.xml index 19710be80b..7aabb6a0e3 100644 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -28,7 +28,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -172,6 +172,30 @@ </execution> </executions> </plugin> + <plugin> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + <executions> + <execution> + <id>get-git-info</id> + <goals> + <goal>revision</goal> + </goals> + <phase>validate</phase> + </execution> + </executions> + <configuration> + <dateFormat>yyyy-MM-dd'T'HH:mm:ss.sss'Z'</dateFormat> + <includeOnlyProperties> + <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty> + <includeOnlyProperty>^git.commit.(id.full|message.short|user.name|user.email)$</includeOnlyProperty> + <includeOnlyProperty>^git.branch</includeOnlyProperty> + </includeOnlyProperties> + <generateGitPropertiesFile>true</generateGitPropertiesFile> + <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename> + <commitIdGenerationMode>full</commitIdGenerationMode> + </configuration> + </plugin> </plugins> </build> <profiles> diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index b97eabacb8..d7e39f7fae 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -180,6 +180,11 @@ management: probes: enabled: true + info: + git: + enabled: true + mode: full + logging: format: json level: @@ -190,7 +195,7 @@ logging: ncmp: policy-executor: enabled: ${POLICY_SERVICE_ENABLED:false} - defaultDecision: "allow" + defaultDecision: ${POLICY_SERVICE_DEFAULT_DECISION:"allow"} server: address: ${POLICY_SERVICE_URL:http://policy-executor-stub} port: ${POLICY_SERVICE_PORT:8093} @@ -228,8 +233,6 @@ ncmp: timers: advised-modules-sync: sleep-time-ms: 5000 - locked-modules-sync: - sleep-time-ms: 15000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: @@ -249,6 +252,7 @@ ncmp: # Custom Hazelcast Config. hazelcast: cluster-name: ${CPS_NCMP_CACHES_CLUSTER_NAME:"cps-and-ncmp-common-cache-cluster"} + instance-config-name: ${CPS_NCMP_INSTANCE_CONFIG_NAME:"cps-and-ncmp-hazelcast-instance-config"} mode: kubernetes: enabled: ${HAZELCAST_MODE_KUBERNETES_ENABLED:false} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java new file mode 100644 index 0000000000..1d39060024 --- /dev/null +++ b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.architecture; + +import org.apache.commons.lang3.ArrayUtils; + +public class ArchitectureTestBase { + + private static final String[] ACCEPTED_3PP_PACKAGES = { "com.fasterxml..", + "com.google..", + "com.hazelcast..", + "edu..", + "io.cloudevents..", + "io.micrometer..", + "io.netty..", + "io.swagger..", + "jakarta..", + "java..", + "lombok..", + "org.apache..", + "org.mapstruct..", + "org.slf4j..", + "org.springframework..", + "reactor.." + }; + + static String[] commonAndListedPackages(final String... packageIdentifiers) { + return ArrayUtils.addAll(ACCEPTED_3PP_PACKAGES, packageIdentifiers); + } +} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java new file mode 100644 index 0000000000..7e96447252 --- /dev/null +++ b/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java @@ -0,0 +1,69 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +@AnalyzeClasses(packages = "org.onap.cps", importOptions = {ImportOption.DoNotIncludeTests.class}) +public class CpsArchitectureTest extends ArchitectureTestBase { + + @ArchTest + static final ArchRule cpsRestControllerShouldOnlyDependOnCpsService = + classes().that().resideInAPackage("org.onap.cps.rest..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.rest..", + "org.onap.cps.api..", + "org.onap.cps.utils..", + // Breaks arch rules + "org.onap.cps.spi..")); + + @ArchTest + static final ArchRule cpsServiceApiShouldNotDependOnAnything = + classes().that().resideInAPackage("org.onap.cps.api.").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages()).allowEmptyShould(true); + + @ArchTest + static final ArchRule cpsServiceImplShouldDependOnServiceAndEventsAndPathParserPackages = + // I think impl package should be moved from the api package. + // So in a way this whole rule is breaking our architecture goals + classes().that().resideInAPackage("org.onap.cps.api.impl..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.api..", + "org.onap.cps.api.impl..", + "org.onap.cps.events..", + "org.onap.cps.impl.utils..", + "org.onap.cps.spi..", + "org.onap.cps.utils..", + "org.onap.cps.cpspath.parser..", + "org.onap.cps.yang..")); + + @ArchTest + static final ArchRule cpsReferenceImplShouldHaveNoDependants = + classes().that().resideInAPackage("org.onap.cps.ri..").should().onlyHaveDependentClassesThat() + .resideInAnyPackage("org.onap.cps.ri.."); + + @ArchTest + static final ArchRule referenceImplShouldOnlyHaveDependantsInReferenceImpl = + classes().that().resideInAPackage("org.onap.cps.ri.repository..").should().onlyHaveDependentClassesThat() + .resideInAnyPackage("org.onap.cps.ri.."); +} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java deleted file mode 100644 index 82fdc7f487..0000000000 --- a/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 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.architecture; - - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; -import static com.tngtech.archunit.library.freeze.FreezingArchRule.freeze; - -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchIgnore; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; - -/** - * Test class responsible for layered architecture. - */ -@AnalyzeClasses(packages = "org.onap.cps", importOptions = {ImportOption.DoNotIncludeTests.class}) -public class LayeredArchitectureTest { - - private static final String REST_CONTROLLER_PACKAGE = "org.onap.cps.rest.."; - private static final String NCMP_REST_PACKAGE = "org.onap.cps.ncmp.rest.."; - private static final String API_SERVICE_PACKAGE = "org.onap.cps.api.."; - private static final String SPI_SERVICE_PACKAGE = "org.onap.cps.ri.."; - private static final String NCMP_SERVICE_PACKAGE = "org.onap.cps.ncmp.api.."; - private static final String SPI_REPOSITORY_PACKAGE = "org.onap.cps.ri.repository.."; - private static final String YANG_SCHEMA_PACKAGE = "org.onap.cps.yang.."; - private static final String NOTIFICATION_PACKAGE = "org.onap.cps.notification.."; - private static final String CPS_UTILS_PACKAGE = "org.onap.cps.utils.."; - private static final String NCMP_INIT_PACKAGE = "org.onap.cps.ncmp.init.."; - private static final String CPS_CACHE_PACKAGE = "org.onap.cps.cache.."; - private static final String CPS_EVENTS_PACKAGE = "org.onap.cps.events.."; - - //TODO We need to revisit these rules, the first one doesn't even make any sense: CPS-2293 - - @ArchTest - static final ArchRule restControllerShouldOnlyDependOnRestController = - classes().that().resideInAPackage(REST_CONTROLLER_PACKAGE).should().onlyHaveDependentClassesThat() - .resideInAPackage(REST_CONTROLLER_PACKAGE); - - @ArchTest - @ArchIgnore - static final ArchRule apiOrSpiServiceShouldOnlyBeDependedOnByControllerAndServicesAndCommonUtilityPackages = - freeze(classes().that().resideInAPackage(API_SERVICE_PACKAGE) - .or().resideInAPackage(SPI_SERVICE_PACKAGE).should().onlyHaveDependentClassesThat() - .resideInAnyPackage(REST_CONTROLLER_PACKAGE, API_SERVICE_PACKAGE, SPI_SERVICE_PACKAGE, NCMP_REST_PACKAGE, - NCMP_SERVICE_PACKAGE, YANG_SCHEMA_PACKAGE, NOTIFICATION_PACKAGE, CPS_UTILS_PACKAGE, NCMP_INIT_PACKAGE, - CPS_CACHE_PACKAGE, CPS_EVENTS_PACKAGE)); - - - @ArchTest - static final ArchRule repositoryShouldOnlyBeDependedOnByServicesAndRepository = - classes().that().resideInAPackage(SPI_REPOSITORY_PACKAGE).should().onlyHaveDependentClassesThat() - .resideInAnyPackage(API_SERVICE_PACKAGE, SPI_SERVICE_PACKAGE, SPI_REPOSITORY_PACKAGE); -} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java new file mode 100644 index 0000000000..5d8c534e35 --- /dev/null +++ b/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 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.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +@AnalyzeClasses(packages = "org.onap.cps", importOptions = {ImportOption.DoNotIncludeTests.class}) +public class NcmpArchitectureTest extends ArchitectureTestBase { + + @ArchTest + static final ArchRule nothingDependsOnCpsNcmpRest = + classes().that().resideInAPackage("org.onap.cps.ncmp.rest..").should().onlyHaveDependentClassesThat() + .resideInAPackage("org.onap.cps.ncmp.rest.."); + + @ArchTest + static final ArchRule ncmpRestControllerShouldOnlyDependOnNcmpService = + classes().that().resideInAPackage("org.onap.cps.ncmp.rest..") + .should() + .onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.rest..", + // Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.spi..", + "org.onap.cps.utils..", + "org.onap.cps.ncmp.impl..")); + + @ArchTest + static final ArchRule ncmpServiceApiShouldOnlyDependOnThirdPartyPackages = + classes().that().resideInAPackage("org.onap.cps.ncmp.api..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages(// Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.impl..", + "org.onap.cps.ncmp.config", + "org.onap.cps.spi..", + "org.onap.cps.utils..")); + + @ArchTest + static final ArchRule ncmpServiceImplShouldOnlyDependOnCpsServiceAndNcmpEvents = + classes().that().resideInAPackage("org.onap.cps.ncmp.impl..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.api..", + "org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.impl..", + "org.onap.cps.ncmp.event..", + "org.onap.cps.ncmp.events..", + "org.onap.cps.ncmp.utils..", + "org.onap.cps.ncmp.config..", + "org.onap.cps.ncmp.exceptions..", + // Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.cpspath..", + "org.onap.cps.events..", + "org.onap.cps.impl..", + "org.onap.cps.spi..", + "org.onap.cps.utils..")); +} + diff --git a/cps-bom/pom.xml b/cps-bom/pom.xml index 31aedab40e..57de09b6f2 100644 --- a/cps-bom/pom.xml +++ b/cps-bom/pom.xml @@ -25,13 +25,13 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-bom</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <description>This artifact contains dependencyManagement declarations of all published CPS components.</description> <properties> - <nexusproxy>https://nexus.onap.org</nexusproxy> + <onap.nexus.url>https://nexus.onap.org</onap.nexus.url> <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> <sonar.skip>true</sonar.skip> <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> @@ -43,7 +43,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.8.2</version> + <version>3.1.2</version> </plugin> </plugins> </pluginManagement> @@ -53,12 +53,12 @@ <repository> <id>ecomp-releases</id> <name>ECOMP Release Repository</name> - <url>${nexusproxy}${releaseNexusPath}</url> + <url>${onap.nexus.url}${releaseNexusPath}</url> </repository> <snapshotRepository> <id>ecomp-snapshots</id> <name>ECOMP Snapshot Repository</name> - <url>${nexusproxy}${snapshotNexusPath}</url> + <url>${onap.nexus.url}${snapshotNexusPath}</url> </snapshotRepository> </distributionManagement> diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index adef9031ab..ad1828ec5c 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -27,7 +27,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-dependencies</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <name>${project.groupId}:${project.artifactId}</name> @@ -35,7 +35,7 @@ <properties> <groovy.version>3.0.18</groovy.version> - <nexusproxy>https://nexus.onap.org</nexusproxy> + <onap.nexus.url>https://nexus.onap.org</onap.nexus.url> <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> <sonar.skip>true</sonar.skip> @@ -51,7 +51,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.8.2</version> + <version>3.1.2</version> </plugin> </plugins> </pluginManagement> @@ -61,12 +61,12 @@ <repository> <id>ecomp-releases</id> <name>ECOMP Release Repository</name> - <url>${nexusproxy}${releaseNexusPath}</url> + <url>${onap.nexus.url}${releaseNexusPath}</url> </repository> <snapshotRepository> <id>ecomp-snapshots</id> <name>ECOMP Snapshot Repository</name> - <url>${nexusproxy}${snapshotNexusPath}</url> + <url>${onap.nexus.url}${snapshotNexusPath}</url> </snapshotRepository> </distributionManagement> @@ -126,12 +126,12 @@ <dependency> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs</artifactId> - <version>4.2.3</version> + <version>4.8.6</version> </dependency> <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>annotations</artifactId> - <version>3.0.1</version> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-annotations</artifactId> + <version>4.8.6</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> diff --git a/cps-events/pom.xml b/cps-events/pom.xml index e5fab868c2..4c951cb362 100644 --- a/cps-events/pom.xml +++ b/cps-events/pom.xml @@ -24,7 +24,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-events/pom.xml b/cps-ncmp-events/pom.xml index b0b28992e7..0ca3a51dc1 100644 --- a/cps-ncmp-events/pom.xml +++ b/cps-ncmp-events/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml index c73a2519ac..67e1ed29ed 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-app</artifactId> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml index eee0431904..aa7e1fb13a 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml @@ -21,7 +21,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-service</artifactId> @@ -63,11 +63,6 @@ <artifactId>spock-spring</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.spockframework</groupId> - <artifactId>spock-core</artifactId> - <scope>test</scope> - </dependency> </dependencies> </project>
\ No newline at end of file diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 183698c4f5..166488793d 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -213,7 +213,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<List<String>> searchCmHandleIds(@Valid final CmHandleQueryParameters body) { + public ResponseEntity<List<String>> searchCmHandleIds(@Valid final CmHandleQueryParameters body, + @Valid final Boolean outputAlternateId) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/cps-ncmp-rest-stub/pom.xml b/cps-ncmp-rest-stub/pom.xml index 01604e89ff..aecdde1528 100644 --- a/cps-ncmp-rest-stub/pom.xml +++ b/cps-ncmp-rest-stub/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 112dddf61c..637a1386f4 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -366,7 +366,7 @@ components: type: array items: type: string - description: targeted cm handles, maximum of 50 supported. If this limit is exceeded the request wil be refused. + description: targeted cm handle references, maximum of 200 supported. If this limit is exceeded the request will be refused. example: [ "da310eecdb8d44c2acc0ddaae01174b1","c748c58f8e0b438f9fd1f28370b17d47" ] examples: @@ -513,6 +513,14 @@ components: schema: type: string example: my-cm-handle-reference + outputAlternateIdOptionInQuery: + name: outputAlternateId + in: query + description: Boolean parameter to determine if returned value(s) will be cm handle ids or alternate ids for a given query + required: false + schema: + type: boolean + default: false moduleNameInQuery: name: module-name in: query diff --git a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml index ea020f9e81..7b1c8d403e 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml @@ -1,6 +1,6 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Bell Canada -# Modifications Copyright (C) 2021-2022 Nordix Foundation +# Modifications Copyright (C) 2021-2024 Nordix Foundation # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -96,15 +96,16 @@ updateDmiRegistration: } ] -getAllCmHandleIdsForRegisteredDmi: +getAllCmHandleReferencesForRegisteredDmi: get: - description: Get all cm handle IDs for a registered DMI plugin + description: Get all cm handle references for a registered DMI plugin tags: - network-cm-proxy-inventory - summary: Get all cm handle IDs for a registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin) - operationId: getAllCmHandleIdsForRegisteredDmi + summary: Get all cm handle references for a registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin) + operationId: getAllCmHandleReferencesForRegisteredDmi parameters: - $ref: 'components.yaml#/components/parameters/dmiPluginIdentifierInQuery' + - $ref: 'components.yaml#/components/parameters/outputAlternateIdOptionInQuery' responses: 200: description: OK @@ -121,11 +122,13 @@ getAllCmHandleIdsForRegisteredDmi: searchCmHandleIds: post: - description: Query and get CMHandleIds for additional properties, public properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin). + description: Query and get cm handle references for additional properties, public properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin). tags: - network-cm-proxy-inventory summary: Query for CM Handle IDs operationId: searchCmHandleIds + parameters: + - $ref: 'components.yaml#/components/parameters/outputAlternateIdOptionInQuery' requestBody: required: true content: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index adb2419c8a..15b8b37231 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -193,7 +193,7 @@ dataOperationForCmHandle: post: tags: - network-cm-proxy - summary: Execute a data operation for group of cm handle ids + summary: Execute a data operation for group of cm handle references description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. A maximum of 200 cm handles per operation is supported. operationId: executeDataOperationForCmHandles parameters: @@ -417,11 +417,13 @@ getCmHandleStateById: searchCmHandleIds: post: - description: Execute cm handle query search and return a list of cm handle ids. Any number of conditions can be applied. To be included in the result a cm-handle must fulfill ALL the conditions. An empty collection will be returned in the case that the cm handle does not match a condition. For more on cm handle query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible to query on any data related to the cm handle. For more on CPS Path please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. + description: Execute cm handle query search and return a list of cm handle references. Any number of conditions can be applied. To be included in the result a cm handle must fulfill ALL the conditions. An empty collection will be returned in the case that the cm handle does not match a condition. For more on cm handle query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible to query on any data related to the cm handle. For more on CPS Path please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. tags: - network-cm-proxy summary: Execute cm handle query upon a given set of query parameters operationId: searchCmHandleIds + parameters: + - $ref: 'components.yaml#/components/parameters/outputAlternateIdOptionInQuery' requestBody: required: true content: diff --git a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml index 8c0ad41705..6c207ee2af 100755 --- a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml +++ b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml @@ -1,6 +1,6 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Bell Canada -# Modifications Copyright (C) 2022-2023 Nordix Foundation +# Modifications Copyright (C) 2022-2024 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,7 @@ openapi: 3.0.3 info: title: NCMP Inventory API description: NCMP Inventory API - version: "3.5.2" + version: "3.5.4" servers: - url: /ncmpInventory components: @@ -34,7 +34,7 @@ paths: $ref: 'ncmp-inventory.yml#/updateDmiRegistration' /v1/ch/cmHandles: - $ref: 'ncmp-inventory.yml#/getAllCmHandleIdsForRegisteredDmi' + $ref: 'ncmp-inventory.yml#/getAllCmHandleReferencesForRegisteredDmi' /v1/ch/searches: $ref: 'ncmp-inventory.yml#/searchCmHandleIds' diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 78fb141820..dd2706a795 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -22,7 +22,7 @@ openapi: 3.0.3 info: title: NCMP to CPS Proxy API description: NCMP to CPS Proxy API - version: "3.5.2" + version: "3.5.4" servers: - url: /ncmp components: diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index adac504eae..a7b2b76cef 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -35,6 +35,7 @@ <properties> <minimum-coverage>0.99</minimum-coverage> + <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> </properties> <dependencies> @@ -211,6 +212,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> + <version>${maven-resources-plugin.version}</version> <executions> <execution> <id>copy-resources</id> @@ -231,6 +233,25 @@ </resources> </configuration> </execution> + <execution> + <id>copy-to-docs-folder</id> + <phase>compile</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/../docs/api/swagger/ncmp</outputDirectory> + <overwrite>true</overwrite> + <resources> + <resource> + <directory>${project.basedir}/target/generated-sources/openapi/</directory> + <includes> + <include>openapi*.yaml</include> + </includes> + </resource> + </resources> + </configuration> + </execution> </executions> </plugin> </plugins> diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 42f709dcf5..3676bb1393 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -268,18 +268,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids + * @param cmHandleQueryParameters the cm handle query parameters + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return collection of cm handle ids */ @Override - public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, + final Boolean outputAlternateId) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); final Collection<String> cmHandleIds - = networkCmProxyInventoryFacade.executeCmHandleIdSearch(cmHandleQueryApiParameters); + = networkCmProxyInventoryFacade.executeCmHandleIdSearch(cmHandleQueryApiParameters, outputAlternateId); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } 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 8aa86ade36..0e27ba9355 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,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2022-2023 Nordix Foundation + * Modifications Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,13 +51,22 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade; private final NcmpRestInputMapper ncmpRestInputMapper; + /** + * Get all cm handle references under a registered DMI plugin. + * + * @param cmHandleQueryParameters DMI plugin identifier + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (False) or alternate id (True) + * @return list of cm handle IDs + */ @Override - public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters) { + public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, + final Boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper .toCmHandleQueryServiceParameters(cmHandleQueryParameters); final Collection<String> cmHandleIds = networkCmProxyInventoryFacade - .executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters); + .executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, outputAlternateId); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } @@ -65,12 +74,17 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor * Get all cm-handle IDs under a registered DMI plugin. * * @param dmiPluginIdentifier DMI plugin identifier + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (False) or alternate id (True) * @return list of cm handle IDs */ @Override - public ResponseEntity<List<String>> getAllCmHandleIdsForRegisteredDmi(final String dmiPluginIdentifier) { + public ResponseEntity<List<String>> getAllCmHandleReferencesForRegisteredDmi(final String dmiPluginIdentifier, + final Boolean outputAlternateId) { + final Collection<String> cmHandleIds = - networkCmProxyInventoryFacade.getAllCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); + networkCmProxyInventoryFacade.getAllCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier, + outputAlternateId); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } @@ -85,7 +99,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor public ResponseEntity updateDmiPluginRegistration( final @Valid RestDmiPluginRegistration restDmiPluginRegistration) { final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule( + networkCmProxyInventoryFacade.updateDmiRegistration( ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration)); final DmiPluginRegistrationErrorResponse failedRegistrationErrorResponse = getFailureRegistrationResponse(dmiPluginRegistrationResponse); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java index 6910003c54..7255743c67 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java @@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException; import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException; import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException; +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException; import org.onap.cps.ncmp.api.exceptions.DmiRequestException; import org.onap.cps.ncmp.api.exceptions.InvalidTopicException; @@ -90,8 +91,8 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.CONFLICT, exception); } - @ExceptionHandler({DataNodeNotFoundException.class}) - public static ResponseEntity<Object> handleNotFoundExceptions(final Exception exception) { + @ExceptionHandler({CmHandleNotFoundException.class, DataNodeNotFoundException.class}) + public static ResponseEntity<Object> cmHandleNotFoundExceptions(final Exception exception) { return buildErrorResponse(HttpStatus.NOT_FOUND, exception); } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DataOperationRequestMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DataOperationRequestMapper.java index 42622a2ca2..1e73aca158 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DataOperationRequestMapper.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DataOperationRequestMapper.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ public interface DataOperationRequestMapper { DataOperationRequest toDataOperationRequest( org.onap.cps.ncmp.rest.model.DataOperationRequest dataOperationRequest); - @Mapping(source = "targetIds", target = "cmHandleIds") + @Mapping(source = "targetIds", target = "cmHandleReferences") DataOperationDefinition toDataOperationDefinition( org.onap.cps.ncmp.rest.model.DataOperationDefinition dataOperationDefinition); } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java index 7492c1fcac..f5804d73ad 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java @@ -36,13 +36,19 @@ public class DeprecationHelper { private final JsonObjectMapper jsonObjectMapper; /** - * Convert the old condition properties to the new schema. - * !!! remove it after the old condition removed !!! - * it only works for module names + * Converts the old condition properties from {@link CmHandleQueryParameters} + * to the new schema defined in {@link CmHandleQueryApiParameters}. * - * @deprecated this method will be removed in Release 12 (No Name know yet) + * <p>This method transforms the old module name-based condition properties to the new format. + * It should only be used for backward compatibility until the old conditions + * are removed in future releases. * - * @param cmHandleQueryParameters the original input parameter + * <p><b>Important:</b> This method will be removed in next release(release name not finalized yet).</p> + * + * @param cmHandleQueryParameters the original query parameters containing old condition properties + * @return an instance of {@link CmHandleQueryApiParameters} with the transformed condition properties + * @deprecated This method is deprecated and will be removed in Release 12. + * Use the new condition handling approach instead. */ @Deprecated public CmHandleQueryApiParameters mapOldConditionProperties( @@ -69,7 +75,6 @@ public class DeprecationHelper { } ); } - return cmHandleQueryApiParameters; } } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index 43403fa890..5340c6d93f 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -363,7 +363,7 @@ class NetworkCmProxyControllerSpec extends Specification { given: 'an endpoint and json data' def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" and: 'the service method is invoked with module names and returns cm handle ids' - 1 * mockNetworkCmProxyInventoryFacade.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2'] + 1 * mockNetworkCmProxyInventoryFacade.executeCmHandleIdSearch(_, _) >> ['ch-1', 'ch-2'] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response then: 'cm handle ids are returned' 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 97c68f08f3..59307640ef 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,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * Modifications Copyright (C) 2021-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { .content(jsonData) ).andReturn().response then: 'the converted object is forwarded to the registration service' - 1 * mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse() + 1 * mockNetworkCmProxyInventoryFacade.updateDmiRegistration(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse() and: 'response status is no content' response.status == HttpStatus.OK.value() where: 'the following registration json is used' @@ -113,7 +113,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { and: 'the mapper service returns a converted object' ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters and: 'the service returns the desired results' - mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse + mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, _) >> serviceMockResponse when: 'post request is performed & search is called with the given request parameters' def response = mvc.perform( post("$ncmpBasePathV1/ch/searches") @@ -136,7 +136,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { and: 'the mapper service returns a converted object' ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters and: 'the service returns the desired results' - mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse + mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, _) >> serviceMockResponse when: 'post request is performed & search is called with the given request parameters' def response = mvc.perform( post("$ncmpBasePathV1/ch/searches") @@ -181,7 +181,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { updatedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-2')], removedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-3')] ) - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> dmiRegistrationResponse when: 'registration endpoint is invoked' def response = mvc.perform( post("$ncmpBasePathV1/ch") @@ -205,7 +205,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { removedCmHandles: [removeCmHandleResponse], upgradedCmHandles: [upgradeCmHandleResponse] ) - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> dmiRegistrationResponse when: 'registration endpoint is invoked' def response = mvc.perform( post("$ncmpBasePathV1/ch") @@ -234,12 +234,14 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { 'delete upgrade failed' | expectedSuccessResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedFailedResponse('cm-handle-4') || [] | [] | [expectedUnknownErrorResponse('cm-handle-3')] | [expectedUnknownErrorResponse('cm-handle-4')] } - def 'Get all cm handle IDs by DMI plugin identifier.'() { - given: 'an endpoint for returning cm handle IDs for a registered dmi plugin' - def getUrl = "$ncmpBasePathV1/ch/cmHandles?dmi-plugin-identifier=some-dmi-plugin-identifier" - and: 'a collection of cm handle IDs are returned' - 1 * mockNetworkCmProxyInventoryFacade.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') + def 'Get all cm handle references by DMI plugin identifier when #scenario.'() { + given: 'an endpoint for returning cm handle references for a registered dmi plugin' + def getUrl = "$ncmpBasePathV1/ch/cmHandles?dmi-plugin-identifier=some-dmi-plugin-identifier"+outputAlternateId + and: 'a collection of cm handle references are returned' + mockNetworkCmProxyInventoryFacade.getAllCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', false) >> ['cm-handle-id-1','cm-handle-id-2'] + mockNetworkCmProxyInventoryFacade.getAllCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', true) + >> ['alternate-id-1','alternate-id-2'] when: 'the endpoint is invoked' def response = mvc.perform( get(getUrl) @@ -247,8 +249,12 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { .accept(MediaType.APPLICATION_JSON_VALUE) ).andReturn().response then: 'the response matches the result returned by the service layer' - assert response.contentAsString.contains('cm-handle-id-1') - assert response.contentAsString.contains('cm-handle-id-2') + assert response.contentAsString.contains(firstReference) + assert response.contentAsString.contains(secondReference) + where: + scenario | outputAlternateId || firstReference | secondReference + 'output returns cm handle ids' | '' || 'cm-handle-id-1' | 'cm-handle-id-2' + 'output returns alternate ids' | '&outputAlternateId=true' || 'alternate-id-1' | 'alternate-id-2' } def expectedUnknownErrorResponse(cmHandle) { diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index 9d36d106c7..c75058bfd6 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -35,7 +35,6 @@ import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade -import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.rest.util.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper @@ -129,19 +128,19 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { then: 'an HTTP response is returned with correct message and details' assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails) where: - scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails - 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails - 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null - 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null - 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null - 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null - 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null - 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null - 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found' - 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists' - 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist' - 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs' - 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails) || CONFLICT | sampleErrorMessage | sampleErrorDetails + scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails + 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails + 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null + 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null + 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null + 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null + 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null + 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null + 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found' + 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists' + 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist' + 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs' + 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails, null) || CONFLICT | sampleErrorMessage | sampleErrorDetails } def 'Post request with exception returns correct HTTP Status.'() { @@ -170,7 +169,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { if (NCMP == apiType) { mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences(*_) >> { throw exception } } - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> { throw exception } + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> { throw exception } } def performTestRequest(apiType) { diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 7871aafd6b..fda4221f6f 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -74,10 +74,6 @@ <artifactId>cps-path-parser</artifactId> </dependency> <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>annotations</artifactId> - </dependency> - <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-spring</artifactId> </dependency> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java index 8cfad7dbf6..be22752882 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java @@ -27,14 +27,14 @@ public enum NcmpResponseStatus { SUCCESS("0", "Successfully applied changes"), CM_DATA_SUBSCRIPTION_ACCEPTED("1", "ACCEPTED"), - CM_HANDLES_NOT_FOUND("100", "cm handle id(s) not found"), + CM_HANDLES_NOT_FOUND("100", "cm handle reference(s) not found"), CM_HANDLES_NOT_READY("101", "cm handle(s) not ready"), DMI_SERVICE_NOT_RESPONDING("102", "dmi plugin service is not responding"), UNABLE_TO_READ_RESOURCE_DATA("103", "dmi plugin service is not able to read resource data"), CM_DATA_SUBSCRIPTION_REJECTED("104", "REJECTED"), UNKNOWN_ERROR("108", "Unknown error"), CM_HANDLE_ALREADY_EXIST("109", "cm-handle already exists"), - CM_HANDLE_INVALID_ID("110", "cm-handle has an invalid character(s) in id"), + CM_HANDLE_INVALID_ID("110", "cm handle reference has an invalid character(s) in id"), ALTERNATE_ID_ALREADY_ASSOCIATED("111", "alternate id already associated"), MESSAGE_TOO_LARGE("112", "message too large"); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationDefinition.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationDefinition.java index d1ff1a5815..79da44af8f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationDefinition.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationDefinition.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,5 +45,5 @@ public class DataOperationDefinition { @JsonProperty("targetIds") @Valid - private List<String> cmHandleIds = new ArrayList<>(); + private List<String> cmHandleReferences = new ArrayList<>(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/CmHandleNotFoundException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/CmHandleNotFoundException.java new file mode 100644 index 0000000000..715e1a00db --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/CmHandleNotFoundException.java @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.exceptions; + +public class CmHandleNotFoundException extends NcmpException { + + private static final String CM_HANDLE_NOT_FOUND_DETAILS_FORMAT = + "No cm handles found with reference %s"; + + /** + * Constructor. + * + * @param cmHandleReference cm handle reference either cm handle id or alternate id + */ + public CmHandleNotFoundException(final String cmHandleReference) { + super("Cm handle not found", String.format(CM_HANDLE_NOT_FOUND_DETAILS_FORMAT, + cmHandleReference)); + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/NcmpException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/NcmpException.java index 6754965866..3c81d0f536 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/NcmpException.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/NcmpException.java @@ -39,7 +39,18 @@ public class NcmpException extends RuntimeException { * @param details the error details */ public NcmpException(final String message, final String details) { - super(message); + this(message, details, null); + } + + /** + * Constructor with cause. + * + * @param message the error message + * @param details the error details + * @param cause the cause of the exception + */ + public NcmpException(final String message, final String details, final Throwable cause) { + super(message, cause); this.details = details; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java index 333c12271b..bb753b85f1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java @@ -35,8 +35,9 @@ public class PolicyExecutorException extends NcmpException { * * @param message response message * @param details response details + * @param cause the cause of the exception */ - public PolicyExecutorException(final String message, final String details) { - super(message, details); + public PolicyExecutorException(final String message, final String details, final Throwable cause) { + super(message, details, cause); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java index 1fa801c3c5..f0547d3d24 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -29,7 +29,6 @@ import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator. import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.CompositeState; @@ -51,7 +50,6 @@ import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyInventoryFacade { @@ -64,37 +62,45 @@ public class NetworkCmProxyInventoryFacade { private final TrustLevelManager trustLevelManager; private final AlternateIdMatcher alternateIdMatcher; + + /** * Registration of Created, Removed, Updated or Upgraded CM Handles. * * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { - return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration); + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { + return cmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration); } /** - * Get all cm handle IDs by DMI plugin identifier. + * Get all cm handle references by DMI plugin identifier. * * @param dmiPluginIdentifier DMI plugin identifier - * @return collection of cm handle IDs + * @param outputAlternateId boolean for cm handle reference type either + * cm handle id (false) or alternate id (true) + * @return collection of cm handle references */ - public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) { - return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); + public Collection<String> getAllCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier, + final boolean outputAlternateId) { + return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier, outputAlternateId); } /** * Get all cm handle IDs by various properties. * * @param cmHandleQueryServiceParameters cm handle query parameters - * @return collection of cm handle IDs + * @param outputAlternateId boolean for cm handle reference type either + * cm handle id (false) or alternate id (true) + * @return collection of cm handle references */ public Collection<String> executeParameterizedCmHandleIdSearch( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters); + + return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters, + outputAlternateId); } @@ -156,13 +162,16 @@ public class NetworkCmProxyInventoryFacade { * Retrieve cm handle ids for the given query parameters. * * @param cmHandleQueryApiParameters cm handle query parameters + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return cm handle ids */ - public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, + final boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); + return parameterizedCmHandleQueryService.queryCmHandleReferenceIds(cmHandleQueryServiceParameters, + outputAlternateId); } /** @@ -218,4 +227,4 @@ public class NetworkCmProxyInventoryFacade { .getEffectiveTrustLevel(ncmpServiceCmHandle.getCmHandleId())); } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java index d911fc61b9..109a541cb3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java @@ -40,19 +40,30 @@ public class HazelcastCacheConfig { @Value("${hazelcast.cluster-name}") protected String clusterName; + @Value("${hazelcast.instance-config-name}") + protected String instanceConfigName; + @Value("${hazelcast.mode.kubernetes.enabled}") protected boolean cacheKubernetesEnabled; @Value("${hazelcast.mode.kubernetes.service-name}") protected String cacheKubernetesServiceName; - protected HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName, - final NamedConfig namedConfig) { - return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig)); + protected HazelcastInstance getOrCreateHazelcastInstance(final NamedConfig namedConfig) { + return Hazelcast.getOrCreateHazelcastInstance(defineInstanceConfig(instanceConfigName, namedConfig)); + } + + private Config defineInstanceConfig(final String instanceConfigName, final NamedConfig namedConfig) { + final Config config = getHazelcastInstanceConfig(instanceConfigName); + config.setClusterName(clusterName); + config.setClassLoader(org.onap.cps.spi.model.Dataspace.class.getClassLoader()); + configureDataStructures(namedConfig, config); + exposeClusterInformation(config); + updateDiscoveryMode(config); + return config; } - private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) { - final Config config = new Config(instanceName); + private static void configureDataStructures(final NamedConfig namedConfig, final Config config) { if (namedConfig instanceof MapConfig) { config.addMapConfig((MapConfig) namedConfig); } @@ -62,11 +73,16 @@ public class HazelcastCacheConfig { if (namedConfig instanceof SetConfig) { config.addSetConfig((SetConfig) namedConfig); } + } - config.setClusterName(clusterName); - config.setClassLoader(org.onap.cps.spi.model.Dataspace.class.getClassLoader()); - exposeClusterInformation(config); - updateDiscoveryMode(config); + private Config getHazelcastInstanceConfig(final String instanceConfigName) { + final HazelcastInstance hazelcastInstance = Hazelcast.getHazelcastInstanceByName(instanceConfigName); + Config config = null; + if (hazelcastInstance != null) { + config = hazelcastInstance.getConfig(); + } else { + config = new Config(instanceConfigName); + } return config; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfig.java index e890d7288c..cb5102063e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfig.java @@ -43,7 +43,7 @@ public class CmSubscriptionConfig extends HazelcastCacheConfig { */ @Bean public IMap<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache() { - return createHazelcastInstance("hazelCastInstanceCmNotificationSubscription", - cmNotificationSubscriptionCacheMapConfig).getMap("cmNotificationSubscriptionCache"); + return getOrCreateHazelcastInstance(cmNotificationSubscriptionCacheMapConfig).getMap( + "cmNotificationSubscriptionCache"); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumer.java index 9e90eabbc4..2d1f64802b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumer.java @@ -46,7 +46,8 @@ public class CmAvcEventConsumer { private final EventsPublisher<CloudEvent> eventsPublisher; /** - * Incoming AvcEvent in the form of Consumer Record. + * Incoming Cm AvcEvent in the form of Consumer Record, it will be forwarded as is to a target topic. + * The key from incoming record will be used as key for the target topic as well to preserve the message ordering. * * @param cmAvcEventAsConsumerRecord Incoming raw consumer record */ @@ -55,7 +56,8 @@ public class CmAvcEventConsumer { public void consumeAndForward( final ConsumerRecord<String, CloudEvent> cmAvcEventAsConsumerRecord) { final CloudEvent outgoingAvcEvent = cmAvcEventAsConsumerRecord.value(); - log.debug("Consuming AVC event {} ...", outgoingAvcEvent); - eventsPublisher.publishCloudEvent(cmEventsTopicName, outgoingAvcEvent.getId(), outgoingAvcEvent); + final String outgoingAvcEventKey = cmAvcEventAsConsumerRecord.key(); + log.debug("Consuming AVC event with key : {} and value : {}", outgoingAvcEventKey, outgoingAvcEvent); + eventsPublisher.publishCloudEvent(cmEventsTopicName, outgoingAvcEventKey, outgoingAvcEvent); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java index 301b8195e4..c4bdc1cf1b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java @@ -139,10 +139,10 @@ public class DmiDataOperations { final String requestId, final String authorization) { - final Set<String> cmHandlesIds = getDistinctCmHandleIds(dataOperationRequest); + final Set<String> cmHandlesReferences = getDistinctCmHandleReferences(dataOperationRequest); final Collection<YangModelCmHandle> yangModelCmHandles - = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); + = inventoryPersistence.getYangModelCmHandlesFromCmHandleReferences(cmHandlesReferences); final Map<String, List<DmiDataOperation>> operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(topicParamInQuery, @@ -246,10 +246,10 @@ public class DmiDataOperations { } } - private static Set<String> getDistinctCmHandleIds(final DataOperationRequest dataOperationRequest) { + private static Set<String> getDistinctCmHandleReferences(final DataOperationRequest dataOperationRequest) { return dataOperationRequest.getDataOperationDefinitions().stream() .flatMap(dataOperationDefinition -> - dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet()); + dataOperationDefinition.getCmHandleReferences().stream()).collect(Collectors.toSet()); } private void asyncSendMultipleRequest(final String requestId, final String topicParamInQuery, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java index a21210c376..8920839cd5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java @@ -91,10 +91,10 @@ public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestH throw new InvalidDatastoreException(dataOperationDefinition.getDatastore() + " datastore is not supported"); } - if (dataOperationDefinition.getCmHandleIds().size() > MAXIMUM_CM_HANDLES_PER_OPERATION) { + if (dataOperationDefinition.getCmHandleReferences().size() > MAXIMUM_CM_HANDLES_PER_OPERATION) { final String errorMessage = String.format(PAYLOAD_TOO_LARGE_TEMPLATE, dataOperationDefinition.getOperationId(), - dataOperationDefinition.getCmHandleIds().size()); + dataOperationDefinition.getCmHandleReferences().size()); throw new PayloadTooLargeException(errorMessage); } }); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java index 5343a2e131..b9b295a8a0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java @@ -28,16 +28,13 @@ import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL; import java.util.Collection; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.CmResourceAddress; import org.onap.cps.ncmp.api.data.models.DataOperationRequest; import org.onap.cps.ncmp.api.data.models.DatastoreType; import org.onap.cps.ncmp.api.data.models.OperationType; -import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyFacade { @@ -45,7 +42,6 @@ public class NetworkCmProxyFacade { private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; private final DmiDataOperations dmiDataOperations; - private final AlternateIdMatcher alternateIdMatcher; /** * Fetches resource data for a given data store using DMI (Data Management Interface). diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java index caed28a648..af4331893d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.impl.data.policyexecutor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.net.UnknownHostException; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Collections; @@ -45,6 +46,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; @Slf4j @Service @@ -71,6 +73,8 @@ public class PolicyExecutor { private final ObjectMapper objectMapper; + private static final Throwable NO_ERROR = null; + /** * Use the Policy Executor to check permission for a cm write operation. * Wil throw an exception when the operation is not permitted (work in progress) @@ -88,26 +92,20 @@ public class PolicyExecutor { final String changeRequestAsJson) { log.trace("Policy Executor Enabled: {}", enabled); if (enabled) { - ResponseEntity<JsonNode> responseEntity = null; try { - responseEntity = - getPolicyExecutorResponse(yangModelCmHandle, operationType, authorization, resourceIdentifier, - changeRequestAsJson); - } catch (final RuntimeException runtimeException) { - processException(runtimeException); - } - if (responseEntity == null) { - log.warn("No valid response from Policy Executor, ignored"); - return; - } - if (responseEntity.getStatusCode().is2xxSuccessful()) { - if (responseEntity.getBody() == null) { + final ResponseEntity<JsonNode> responseEntity = getPolicyExecutorResponse(yangModelCmHandle, + operationType, + authorization, + resourceIdentifier, + changeRequestAsJson); + final JsonNode responseBody = responseEntity.getBody(); + if (responseBody == null) { log.warn("No valid response body from Policy Executor, ignored"); return; } - processSuccessResponse(responseEntity.getBody()); - } else { - processNon2xxResponse(responseEntity.getStatusCode().value()); + processSuccessResponse(responseBody); + } catch (final RuntimeException runtimeException) { + processException(runtimeException); } } } @@ -173,35 +171,17 @@ public class PolicyExecutor { .block(); } - private void processNon2xxResponse(final int httpStatusCode) { - processFallbackResponse("Policy Executor returned HTTP Status code " + httpStatusCode + "."); - } - - private void processException(final RuntimeException runtimeException) { - if (runtimeException.getCause() instanceof TimeoutException) { - processFallbackResponse("Policy Executor request timed out."); - } else { - log.warn("Request to Policy Executor failed with unexpected exception", runtimeException); - throw runtimeException; - } - } - - private void processFallbackResponse(final String message) { - final String decisionId = "N/A"; - final String decision = defaultDecision; - final String warning = message + " Falling back to configured default decision: " + defaultDecision; - log.warn(warning); - processDecision(decisionId, decision, warning); - } - private static void processSuccessResponse(final JsonNode responseBody) { final String decisionId = responseBody.path("decisionId").asText("unknown id"); final String decision = responseBody.path("decision").asText("unknown"); final String messageFromPolicyExecutor = responseBody.path("message").asText(); - processDecision(decisionId, decision, messageFromPolicyExecutor); + processDecision(decisionId, decision, messageFromPolicyExecutor, NO_ERROR); } - private static void processDecision(final String decisionId, final String decision, final String details) { + private static void processDecision(final String decisionId, + final String decision, + final String details, + final Throwable optionalCauseOfError) { log.trace("Policy Executor decision id: {} ", decisionId); if ("allow".equals(decision)) { log.trace("Operation allowed."); @@ -209,8 +189,37 @@ public class PolicyExecutor { log.warn("Policy Executor decision: {}", decision); log.warn("Policy Executor message: {}", details); final String message = "Operation not allowed. Decision id " + decisionId + " : " + decision; - throw new PolicyExecutorException(message, details); + throw new PolicyExecutorException(message, details, optionalCauseOfError); } } + private void processException(final RuntimeException runtimeException) { + if (runtimeException instanceof WebClientResponseException) { + final WebClientResponseException webClientResponseException = (WebClientResponseException) runtimeException; + final int httpStatusCode = webClientResponseException.getStatusCode().value(); + processFallbackResponse("Policy Executor returned HTTP Status code " + httpStatusCode + ".", + webClientResponseException); + } else { + final Throwable cause = runtimeException.getCause(); + if (cause instanceof TimeoutException) { + processFallbackResponse("Policy Executor request timed out.", cause); + } else if (cause instanceof UnknownHostException) { + final String message + = String.format("Cannot connect to Policy Executor (%s:%s).", serverAddress, serverPort); + processFallbackResponse(message, cause); + } else { + log.warn("Request to Policy Executor failed with unexpected exception", runtimeException); + throw runtimeException; + } + } + } + + private void processFallbackResponse(final String message, final Throwable cause) { + final String decisionId = "N/A"; + final String decision = defaultDecision; + final String warning = message + " Falling back to configured default decision: " + defaultDecision; + log.warn(warning); + processDecision(decisionId, decision, warning, cause); + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelper.java index 3104be5539..a0d89644e4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelper.java @@ -31,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -71,8 +70,9 @@ public class DmiDataOperationsHelper { final Map<String, List<DmiDataOperation>> dmiDataOperationsOutPerDmiServiceName = new HashMap<>(); final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, - List<String>>> cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); - final Set<String> nonReadyCmHandleIdsLookup = filterAndGetNonReadyCmHandleIds(yangModelCmHandles); + List<String>>> cmHandleReferencesPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); + final Map<String, String> nonReadyAlternateIdPerCmHandleId = + filterAndGetNonReadyAlternateIdPerCmHandleId(yangModelCmHandles); final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName = DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); @@ -84,17 +84,19 @@ public class DmiDataOperationsHelper { for (final DataOperationDefinition dataOperationDefinitionIn : dataOperationRequestIn.getDataOperationDefinitions()) { - final List<String> nonExistingCmHandleIds = new ArrayList<>(); - final List<String> nonReadyCmHandleIds = new ArrayList<>(); - for (final String cmHandleId : dataOperationDefinitionIn.getCmHandleIds()) { - if (nonReadyCmHandleIdsLookup.contains(cmHandleId)) { - nonReadyCmHandleIds.add(cmHandleId); + final List<String> nonExistingCmHandleReferences = new ArrayList<>(); + final List<String> nonReadyCmHandleReferences = new ArrayList<>(); + for (final String cmHandleReference : dataOperationDefinitionIn.getCmHandleReferences()) { + if (nonReadyAlternateIdPerCmHandleId.containsKey(cmHandleReference) + || nonReadyAlternateIdPerCmHandleId.containsValue(cmHandleReference)) { + nonReadyCmHandleReferences.add(cmHandleReference); } else { + final String cmHandleId = getCmHandleId(cmHandleReference, yangModelCmHandles); final String dmiServiceName = dmiServiceNamesPerCmHandleId.get(cmHandleId); final Map<String, String> cmHandleIdProperties = dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).get(cmHandleId); if (cmHandleIdProperties == null) { - nonExistingCmHandleIds.add(cmHandleId); + nonExistingCmHandleReferences.add(cmHandleReference); } else { final DmiDataOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName, dataOperationDefinitionIn, dmiDataOperationsOutPerDmiServiceName); @@ -105,14 +107,14 @@ public class DmiDataOperationsHelper { } } } - populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation, + populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleReferencesPerResponseCodesPerOperation, DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn), - CM_HANDLES_NOT_FOUND, nonExistingCmHandleIds); - populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation, + CM_HANDLES_NOT_FOUND, nonExistingCmHandleReferences); + populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleReferencesPerResponseCodesPerOperation, DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn), - CM_HANDLES_NOT_READY, nonReadyCmHandleIds); + CM_HANDLES_NOT_READY, nonReadyCmHandleReferences); } - publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation); + publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleReferencesPerResponseCodesPerOperation); return dmiDataOperationsOutPerDmiServiceName; } @@ -182,10 +184,26 @@ public class DmiDataOperationsHelper { return dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1); } - private static Set<String> filterAndGetNonReadyCmHandleIds(final Collection<YangModelCmHandle> yangModelCmHandles) { - return yangModelCmHandles.stream() - .filter(yangModelCmHandle -> yangModelCmHandle.getCompositeState().getCmHandleState() - != CmHandleState.READY).map(YangModelCmHandle::getId).collect(Collectors.toSet()); + private static Map<String, String> filterAndGetNonReadyAlternateIdPerCmHandleId( + final Collection<YangModelCmHandle> yangModelCmHandles) { + final Map<String, String> cmHandleReferenceMap = new HashMap<>(yangModelCmHandles.size()); + for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) { + if (yangModelCmHandle.getCompositeState().getCmHandleState() != CmHandleState.READY) { + cmHandleReferenceMap.put(yangModelCmHandle.getId(), yangModelCmHandle.getAlternateId()); + } + } + return cmHandleReferenceMap; + } + + private static String getCmHandleId(final String cmHandleReference, + final Collection<YangModelCmHandle> yangModelCmHandles) { + for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) { + if (cmHandleReference.equals(yangModelCmHandle.getId()) + || cmHandleReference.equals(yangModelCmHandle.getAlternateId())) { + return yangModelCmHandle.getId(); + } + } + return cmHandleReference; } private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<DmiDataOperation, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java index 70d08dccdc..e13d3c2328 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java @@ -32,6 +32,7 @@ import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation; import org.onap.cps.ncmp.api.datajobs.models.ProducerKey; import org.onap.cps.ncmp.api.datajobs.models.WriteOperation; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.models.RequiredDmiService; import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.model.DataNode; @@ -69,9 +70,11 @@ public class WriteRequestExaminer { final DataNode dataNode = alternateIdMatcher .getCmHandleDataNodeByLongestMatchingAlternateId(writeOperation.path(), PATH_SEPARATOR); - final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, dataNode); + final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(dataNode); + + final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, yangModelCmHandle); - final ProducerKey producerKey = createProducerKey(dataNode); + final ProducerKey producerKey = createProducerKey(yangModelCmHandle); final List<DmiWriteOperation> dmiWriteOperations; if (dmiWriteOperationsPerProducerKey.containsKey(producerKey)) { dmiWriteOperations = dmiWriteOperationsPerProducerKey.get(producerKey); @@ -82,24 +85,23 @@ public class WriteRequestExaminer { dmiWriteOperations.add(dmiWriteOperation); } - private ProducerKey createProducerKey(final DataNode dataNode) { - return new ProducerKey((String) dataNode.getLeaves().get("dmi-service-name"), - (String) dataNode.getLeaves().get("data-producer-identifier")); + private ProducerKey createProducerKey(final YangModelCmHandle yangModelCmHandle) { + return new ProducerKey(yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), + yangModelCmHandle.getDataProducerIdentifier()); } private DmiWriteOperation createDmiWriteOperation(final WriteOperation writeOperation, - final DataNode dataNode) { + final YangModelCmHandle yangModelCmHandle) { return new DmiWriteOperation( writeOperation.path(), writeOperation.op(), - (String) dataNode.getLeaves().get("module-set-tag"), + yangModelCmHandle.getModuleSetTag(), writeOperation.value(), writeOperation.operationId(), - getPrivatePropertiesFromDataNode(dataNode)); + getPrivatePropertiesFromDataNode(yangModelCmHandle)); } - private Map<String, String> getPrivatePropertiesFromDataNode(final DataNode dataNode) { - final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(dataNode); + private Map<String, String> getPrivatePropertiesFromDataNode(final YangModelCmHandle yangModelCmHandle) { final Map<String, String> cmHandleDmiProperties = new LinkedHashMap<>(); yangModelCmHandle.getDmiProperties() .forEach(dmiProperty -> cmHandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue())); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java index 95c3c77b71..415153ddf7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java @@ -32,25 +32,31 @@ public interface CmHandleQueryService { * Query Cm Handles based on additional (private) properties. * * @param additionalPropertyQueryPairs private properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have these private properties */ - Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs); + Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs, + boolean outputAlternateId); /** * Query Cm Handles based on public properties. * * @param publicPropertyQueryPairs public properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return CmHandles which have these public properties */ - Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs); + Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs, + boolean outputAlternateId); /** * Query Cm Handles based on Trust Level. * * @param trustLevelPropertyQueryPairs trust level properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have desired trust level */ - Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs); + Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs, + boolean outputAlternateId); /** * Method which returns cm handles by the cm handles state. @@ -94,11 +100,21 @@ public interface CmHandleQueryService { Collection<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState); /** - * Get all cm handles ids by DMI plugin identifier. + * Get collection of all cm handles references by DMI plugin identifier and alternate id output option. * * @param dmiPluginIdentifier DMI plugin identifier - * @return collection of cm handle ids + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) + * @return collection of cm handle references */ - Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier); + Collection<String> getCmHandleReferencesByDmiPluginIdentifier(String dmiPluginIdentifier, + boolean outputAlternateId); + + /** + * Get map of cmHandle references by DMI plugin identifier. + * + * @param dmiPluginIdentifier DMI plugin identifier + * @return map of cmHandle references key:CmHandleId Value:AlternateId + */ + Map<String, String> getCmHandleReferencesMapByDmiPluginIdentifier(String dmiPluginIdentifier); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java index 71e7384208..1490d69881 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java @@ -29,6 +29,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.stream.Collectors; @@ -53,6 +54,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private static final String DESCENDANT_PATH = "//"; private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; + private static final String ALTERNATE_ID = "alternate-id"; private final CpsDataService cpsDataService; private final CpsQueryService cpsQueryService; @@ -65,21 +67,23 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private final CpsValidator cpsValidator; @Override - public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs) { - return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL); + public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs, + final boolean outputAlternateId) { + return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL, outputAlternateId); } @Override - public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs) { - return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC); + public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs, + final boolean outputAlternateId) { + return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC, outputAlternateId); } @Override - public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs) { + public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs, + final boolean outputAlternateId) { final String trustLevelProperty = trustLevelPropertyQueryPairs.values().iterator().next(); final TrustLevel targetTrustLevel = TrustLevel.valueOf(trustLevelProperty); - - return getCmHandleIdsByTrustLevel(targetTrustLevel); + return getCmHandleReferencesByTrustLevel(targetTrustLevel, outputAlternateId); } @Override @@ -118,49 +122,81 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { } @Override - public Collection<String> getCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) { - final Collection<String> cmHandleIds = new HashSet<>(); + public Collection<String> getCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier, + final boolean outputAlternateId) { + final Collection<String> cmHandleReferences = new HashSet<>(); for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( dmiPluginIdentifier, modelledDmiServiceLeaf.getLeafName())) { - cmHandleIds.add(cmHandleAsDataNode.getLeaves().get("id").toString()); + if (outputAlternateId) { + cmHandleReferences.add(cmHandleAsDataNode.getLeaves().get(ALTERNATE_ID).toString()); + } else { + cmHandleReferences.add(cmHandleAsDataNode.getLeaves().get("id").toString()); + } } } - return cmHandleIds; + return cmHandleReferences; } - private Collection<String> getCmHandleIdsByTrustLevel(final TrustLevel targetTrustLevel) { - final Collection<String> selectedCmHandleIds = new HashSet<>(); + @Override + public Map<String, String> getCmHandleReferencesMapByDmiPluginIdentifier(final String dmiPluginIdentifier) { + final Map<String, String> cmHandleReferencesMap = new HashMap<>(); + for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { + for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( + dmiPluginIdentifier, + modelledDmiServiceLeaf.getLeafName())) { + cmHandleReferencesMap.put(cmHandleAsDataNode.getLeaves().get("id").toString(), + cmHandleAsDataNode.getLeaves().get(ALTERNATE_ID).toString()); + } + } + return cmHandleReferencesMap; + } + + private Collection<String> getCmHandleReferencesByTrustLevel(final TrustLevel targetTrustLevel, + final boolean outputAlternateId) { + final Collection<String> selectedCmHandleReferences = new HashSet<>(); for (final Map.Entry<String, TrustLevel> mapEntry : trustLevelPerDmiPlugin.entrySet()) { final String dmiPluginIdentifier = mapEntry.getKey(); final TrustLevel dmiTrustLevel = mapEntry.getValue(); - final Collection<String> candidateCmHandleIds = getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); - for (final String candidateCmHandleId : candidateCmHandleIds) { - final TrustLevel candidateCmHandleTrustLevel = trustLevelPerCmHandle.get(candidateCmHandleId); + final Map<String, String> candidateCmHandleReferences = + getCmHandleReferencesMapByDmiPluginIdentifier(dmiPluginIdentifier); + for (final Map.Entry<String, String> candidateCmHandleReference : candidateCmHandleReferences.entrySet()) { + final TrustLevel candidateCmHandleTrustLevel = + trustLevelPerCmHandle.get(candidateCmHandleReference.getKey()); final TrustLevel effectiveTrustlevel = candidateCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); if (targetTrustLevel.equals(effectiveTrustlevel)) { - selectedCmHandleIds.add(candidateCmHandleId); + if (outputAlternateId) { + selectedCmHandleReferences.add(candidateCmHandleReference.getValue()); + } else { + selectedCmHandleReferences.add(candidateCmHandleReference.getKey()); + } } } } - - return selectedCmHandleIds; + return selectedCmHandleReferences; } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final boolean outputAlternateId) { + if (outputAlternateId) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get(ALTERNATE_ID)).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } private Collection<String> queryCmHandleAnyProperties( final Map<String, String> propertyQueryPairs, - final PropertyType propertyType) { + final PropertyType propertyType, final boolean outputAlternateId) { if (propertyQueryPairs.isEmpty()) { return Collections.emptySet(); } - Collection<String> cmHandleIds = null; + Collection<String> cmHandleReferences = null; for (final Map.Entry<String, String> publicPropertyQueryPair : propertyQueryPairs.entrySet()) { final String cpsPath = DESCENDANT_PATH + propertyType.getYangContainerName() + "[@name=\"" + publicPropertyQueryPair.getKey() @@ -168,17 +204,18 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { final Collection<DataNode> dataNodes = queryCmHandleAncestorsByCpsPath(cpsPath, OMIT_DESCENDANTS); - if (cmHandleIds == null) { - cmHandleIds = collectCmHandleIdsFromDataNodes(dataNodes); + if (cmHandleReferences == null) { + cmHandleReferences = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); } else { - final Collection<String> cmHandleIdsToRetain = collectCmHandleIdsFromDataNodes(dataNodes); - cmHandleIds.retainAll(cmHandleIdsToRetain); + final Collection<String> cmHandleReferencesToRetain; + cmHandleReferencesToRetain = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); + cmHandleReferences.retainAll(cmHandleReferencesToRetain); } - if (cmHandleIds.isEmpty()) { + if (cmHandleReferences.isEmpty()) { break; } } - return cmHandleIds; + return cmHandleReferences; } private Collection<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java index d9f7e38993..cb55b09d41 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java @@ -87,8 +87,7 @@ public class CmHandleRegistrationService { * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java index a0d3a3eaee..562cb6e0de 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java @@ -72,6 +72,14 @@ public interface InventoryPersistence extends NcmpPersistence { Collection<YangModelCmHandle> getYangModelCmHandles(Collection<String> cmHandleIds); /** + * This method retrieves DMI service name, DMI properties and the state for a given list of cm handle references. + * + * @param cmHandleReferences a list of the ids of the cm handles + * @return collection of yang model cm handles + */ + Collection<YangModelCmHandle> getYangModelCmHandlesFromCmHandleReferences(Collection<String> cmHandleReferences); + + /** * Method to return module definitions by cmHandleId. * * @param cmHandleId cm handle ID @@ -149,9 +157,12 @@ public interface InventoryPersistence extends NcmpPersistence { * get CM handles that has given module names. * * @param moduleNamesForQuery module names - * @return Collection of CM handle Ids + * @param outputAlternateId boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return Collection of CM handle references */ - Collection<String> getCmHandleIdsWithGivenModules(Collection<String> moduleNamesForQuery); + Collection<String> getCmHandleReferencesWithGivenModules(Collection<String> moduleNamesForQuery, + boolean outputAlternateId); /** * Check database if cm handle id exists if not return false. @@ -159,5 +170,5 @@ public interface InventoryPersistence extends NcmpPersistence { * @param cmHandleId cmHandle Id * @return Boolean */ - boolean isExistingCmHandleId(String cmHandleId); + Boolean isExistingCmHandleId(String cmHandleId); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index 06c3f8d2f4..e468ed100a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.impl.inventory; +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import com.google.common.collect.Lists; @@ -33,11 +34,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.impl.utils.CpsValidator; +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; import org.onap.cps.ncmp.api.inventory.models.CompositeState; import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @@ -133,6 +136,19 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } @Override + public Collection<YangModelCmHandle> getYangModelCmHandlesFromCmHandleReferences( + final Collection<String> cmHandleReferences) { + + final String cpsPathForCmHandlesByReferences = getCpsPathForCmHandlesByReferences(cmHandleReferences); + + final Collection<DataNode> cmHandlesAsDataNodes = + cmHandleQueryService.queryNcmpRegistryByCpsPath( + cpsPathForCmHandlesByReferences, INCLUDE_ALL_DESCENDANTS); + + return YangDataConverter.toYangModelCmHandles(cmHandlesAsDataNodes); + } + + @Override public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); } @@ -178,8 +194,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv final Collection<DataNode> dataNodes = cmHandleQueryService .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS); if (dataNodes.isEmpty()) { - throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cpsPathForCmHandleByAlternateId); + throw new CmHandleNotFoundException(alternateId); } return dataNodes.iterator().next(); } @@ -190,7 +205,8 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv return Collections.emptyList(); } final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds); - return cmHandleQueryService.queryNcmpRegistryByCpsPath(cpsPathForCmHandlesByAlternateIds, OMIT_DESCENDANTS); + return cmHandleQueryService.queryNcmpRegistryByCpsPath(cpsPathForCmHandlesByAlternateIds, + INCLUDE_ALL_DESCENDANTS); } @Override @@ -201,12 +217,19 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } @Override - public Collection<String> getCmHandleIdsWithGivenModules(final Collection<String> moduleNamesForQuery) { - return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + public Collection<String> getCmHandleReferencesWithGivenModules(final Collection<String> moduleNamesForQuery, + final boolean outputAlternateId) { + if (outputAlternateId) { + final Collection<String> cmHandleIds = + cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + return getAlternateIdsFromDataNodes(getCmHandleDataNodes(cmHandleIds)); + } else { + return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + } } @Override - public boolean isExistingCmHandleId(final String cmHandleId) { + public Boolean isExistingCmHandleId(final String cmHandleId) { try { return !getCmHandleDataNodeByCmHandleId(cmHandleId).isEmpty(); } catch (final DataNodeNotFoundException exception) { @@ -227,6 +250,12 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='", "']")); } + private String getCpsPathForCmHandlesByReferences(final Collection<String> cmHandleReferences) { + return cmHandleReferences.stream() + .flatMap(id -> Stream.of("@id='" + id + "'", "@alternate-id='" + id + "'")) + .collect(Collectors.joining(" or ", NCMP_DMI_REGISTRY_PARENT + "/cm-handles[", "]")); + } + private static String createStateJsonData(final String state) { return "{\"state\":" + state + "}"; } @@ -234,4 +263,9 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) { return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } + + private Collection<String> getAlternateIdsFromDataNodes(final Collection<DataNode> dataNodes) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java index e5848c0dfa..3db4920d3e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java @@ -26,19 +26,22 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; public interface ParameterizedCmHandleQueryService { /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * modules * cps-path * * @param cmHandleQueryServiceParameters the cm handle query parameters - * @return collection of cm handle ids + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return collection of cm handle ids or alternate ids */ - Collection<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleReferenceIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + boolean outputAlternateId); /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * private (additional) properties @@ -46,9 +49,12 @@ public interface ParameterizedCmHandleQueryService { * The inventory interface also allows conditions on private (additional) properties and dmi names * * @param cmHandleQueryServiceParameters the cm handle query parameters + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) * @return collection of cm handle ids */ - Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + boolean outputAlternateId); /** * Query and return cm handle objects that match the given query parameters. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java index 34eeaccf8f..c0d4b08f3c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java @@ -37,10 +37,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; @@ -54,7 +53,6 @@ import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; @Service -@Slf4j @RequiredArgsConstructor public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService { @@ -63,19 +61,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan private final InventoryPersistence inventoryPersistence; @Override - public Collection<String> queryCmHandleIds( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, - this::executeCpsPathQuery, - this::queryCmHandlesByPublicProperties, - this::executeModuleNameQuery, - this::queryCmHandlesByTrustLevel); + public Collection<String> queryCmHandleReferenceIds( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, + this::executeCpsPathQuery, + this::queryCmHandlesByPublicProperties, + this::executeModuleNameQuery, + this::queryCmHandlesByTrustLevel); } @Override public Collection<String> queryCmHandleIdsForInventory( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, this::executeCpsPathQuery, this::queryCmHandlesByPublicProperties, this::queryCmHandlesByPrivateProperties, @@ -90,7 +90,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return getAllCmHandles(); } - final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters); + final Collection<String> cmHandleIds = queryCmHandleReferenceIds(cmHandleQueryServiceParameters, false); return getNcmpServiceCmHandles(cmHandleIds); } @@ -102,7 +102,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> queryCmHandlesByDmiPlugin( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { final Map<String, String> dmiPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), InventoryQueryConditions.CM_HANDLE_WITH_DMI_PLUGIN.getName()); @@ -113,11 +113,17 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan final String dmiPluginIdentifierValue = dmiPropertyQueryPairs .get(PropertyType.DMI_PLUGIN.getYangContainerName()); - return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + if (outputAlternateId) { + return + cmHandleQueryService.getCmHandleReferencesMapByDmiPluginIdentifier(dmiPluginIdentifierValue).values(); + } else { + return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifierValue, + outputAlternateId); + } } private Collection<String> queryCmHandlesByPrivateProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { final Map<String, String> privatePropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -126,11 +132,11 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (privatePropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs); + return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByPublicProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { final Map<String, String> publicPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -139,11 +145,12 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (publicPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByTrustLevel(final CmHandleQueryServiceParameters - cmHandleQueryServiceParameters) { + cmHandleQueryServiceParameters, + final boolean outputAlternateId) { final Map<String, String> trustLevelPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -152,21 +159,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (trustLevelPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId); } private Collection<String> executeModuleNameQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { final Collection<String> moduleNamesForQuery = getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (moduleNamesForQuery.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery); + return inventoryPersistence.getCmHandleReferencesWithGivenModules(moduleNamesForQuery, outputAlternateId); } private Collection<String> executeCpsPathQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { final Map<String, String> cpsPathCondition = getCpsPathCondition(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (!validateCpsPathConditionProperties(cpsPathCondition)) { @@ -177,9 +184,9 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return NO_QUERY_TO_EXECUTE; } try { - cpsPathQueryResult = collectCmHandleIdsFromDataNodes( - cmHandleQueryService.queryCmHandleAncestorsByCpsPath( - cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS)); + cpsPathQueryResult = collectCmHandleReferencesFromDataNodes( + cmHandleQueryService.queryCmHandleAncestorsByCpsPath(cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS), + outputAlternateId); } catch (final PathParsingException pathParsingException) { throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), pathParsingException); @@ -220,10 +227,10 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return Collections.emptyList(); } - private Collection<String> getAllCmHandleIds() { + private Collection<String> getAllCmHandleReferences(final boolean outputAlternateId) { final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY) - .iterator().next(); - return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes()); + .iterator().next(); + return collectCmHandleReferencesFromDataNodes(dataNode.getChildDataNodes(), outputAlternateId); } private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) { @@ -243,14 +250,17 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Function<CmHandleQueryServiceParameters, Collection<String>>... - queryFunctions) { + final boolean outputAlternateId, + final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>>... queryFunctions) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { - return getAllCmHandleIds(); + return getAllCmHandleReferences(outputAlternateId); } Collection<String> combinedQueryResult = NO_QUERY_TO_EXECUTE; - for (final Function<CmHandleQueryServiceParameters, Collection<String>> queryFunction : queryFunctions) { - final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters); + for (final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>> queryFunction : queryFunctions) { + final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters, + outputAlternateId); if (noEntriesFoundCanStopQuerying(queryResult)) { return Collections.emptySet(); } @@ -277,8 +287,14 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final boolean outputAlternateId) { + if (outputAlternateId) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java index b8bb64f537..80bc4ab69f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,9 +68,9 @@ public class AsyncTaskExecutor { private void handleTaskCompletion(final Object response, final Throwable throwable) { if (throwable != null) { if (throwable instanceof TimeoutException) { - log.warn("Async task didn't completed within the required time."); + log.error("Async task didn't complete within the required time.", throwable); } else { - log.debug("Watchdog async batch failed. caused by : {}", throwable.getMessage()); + log.error("Watchdog async batch failed.", throwable); } } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index c6deb79d4d..31fcbad08b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -58,64 +58,65 @@ public class ModuleSyncTasks { */ public CompletableFuture<Void> performModuleSync(final Collection<DataNode> cmHandlesAsDataNodes, final AtomicInteger batchCounter) { + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = + new HashMap<>(cmHandlesAsDataNodes.size()); try { - final Map<YangModelCmHandle, CmHandleState> cmHandelStatePerCmHandle - = new HashMap<>(cmHandlesAsDataNodes.size()); - for (final DataNode cmHandleAsDataNode : cmHandlesAsDataNodes) { - final String cmHandleId = String.valueOf(cmHandleAsDataNode.getLeaves().get("id")); + cmHandlesAsDataNodes.forEach(cmHandleAsDataNode -> { final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleAsDataNode); - final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); - final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); - try { - if (inUpgrade) { - moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); - } else { - moduleSyncService.deleteSchemaSetIfExists(cmHandleId); - moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); - } - yangModelCmHandle.getCompositeState().setLockReason(null); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY); - } catch (final Exception e) { - log.warn("Processing of {} module failed due to reason {}.", cmHandleId, e.getMessage()); - final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED - : LockReasonCategory.MODULE_SYNC_FAILED; - moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, - lockReasonCategory, e.getMessage()); - setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED); - } - log.info("{} is now in {} state", cmHandleId, cmHandelStatePerCmHandle.get(yangModelCmHandle).name()); - } - lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle); + cmHandleStatePerCmHandle.put(yangModelCmHandle, processCmHandle(yangModelCmHandle)); + }); } finally { batchCounter.getAndDecrement(); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get()); } return CompletableFuture.completedFuture(null); } /** - * Resets the state of failed CM handles and updates their status to ADVISED for retry. - - * This method processes a collection of failed CM handles, logs their lock reason, and resets their state + * Set the state of CM handles to ADVISED. + * This method processes a collection of CM handles, logs their lock reason, and resets their state * to ADVISED. Once reset, it updates the CM handle states in a batch to allow for re-attempt by the module-sync * watchdog. * - * @param failedCmHandles a collection of CM handles that have failed and need their state reset + * @param yangModelCmHandles a collection of CM handles that needs their state reset */ - public void resetFailedCmHandles(final Collection<YangModelCmHandle> failedCmHandles) { - final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(failedCmHandles.size()); - for (final YangModelCmHandle failedCmHandle : failedCmHandles) { - final CompositeState compositeState = failedCmHandle.getCompositeState(); - final String resetCmHandleId = failedCmHandle.getId(); + public void setCmHandlesToAdvised(final Collection<YangModelCmHandle> yangModelCmHandles) { + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(yangModelCmHandles.size()); + for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) { + final CompositeState compositeState = yangModelCmHandle.getCompositeState(); + final String resetCmHandleId = yangModelCmHandle.getId(); log.debug("Resetting CM handle {} state to ADVISED for retry by the module-sync watchdog. Lock reason: {}", - failedCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); - cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED); + yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); + cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED); removeResetCmHandleFromModuleSyncMap(resetCmHandleId); } lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); } + private CmHandleState processCmHandle(final YangModelCmHandle yangModelCmHandle) { + final CompositeState compositeState = inventoryPersistence.getCmHandleState(yangModelCmHandle.getId()); + final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); + try { + if (inUpgrade) { + moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); + } else { + moduleSyncService.deleteSchemaSetIfExists(yangModelCmHandle.getId()); + moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); + } + yangModelCmHandle.getCompositeState().setLockReason(null); + return CmHandleState.READY; + } catch (final Exception e) { + log.warn("Processing of {} module failed due to reason {}.", yangModelCmHandle.getId(), e.getMessage()); + final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED + : LockReasonCategory.MODULE_SYNC_FAILED; + moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, + lockReasonCategory, e.getMessage()); + setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); + return CmHandleState.LOCKED; + } + } + private void setCmHandleStateLocked(final YangModelCmHandle advisedCmHandle, final CompositeState.LockReason lockReason) { advisedCmHandle.getCompositeState().setLockReason(lockReason); @@ -126,4 +127,4 @@ public class ModuleSyncTasks { log.info("{} removed from in progress map", resetCmHandleId); } } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index bc7d6cdf67..898b8d5bf4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -27,10 +27,12 @@ import java.util.HashSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.Sleeper; import org.onap.cps.spi.model.DataNode; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -45,6 +47,9 @@ public class ModuleSyncWatchdog { private final IMap<String, Object> moduleSyncStartedOnCmHandles; private final ModuleSyncTasks moduleSyncTasks; private final AsyncTaskExecutor asyncTaskExecutor; + private final Lock workQueueLock; + private final Sleeper sleeper; + private static final int MODULE_SYNC_BATCH_SIZE = 100; private static final long PREVENT_CPU_BURN_WAIT_TIME_MILLIS = 10; private static final String VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP = "Started"; @@ -56,11 +61,12 @@ public class ModuleSyncWatchdog { * Check DB for any cm handles in 'ADVISED' state. * Queue and create batches to process them asynchronously. * This method will only finish when there are no more 'ADVISED' cm handles in the DB. - * This method wil be triggered on a configurable interval + * This method is triggered on a configurable interval (ncmp.timers.advised-modules-sync.sleep-time-ms) */ - @Scheduled(fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") + @Scheduled(initialDelayString = "${test.ncmp.timers.advised-modules-sync.initial-delay-ms:0}", + fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") public void moduleSyncAdvisedCmHandles() { - log.info("Processing module sync watchdog waking up."); + log.debug("Processing module sync watchdog waking up."); populateWorkQueueIfNeeded(); while (!moduleSyncWorkQueue.isEmpty()) { if (batchCounter.get() <= asyncTaskExecutor.getAsyncTaskParallelismLevel()) { @@ -80,36 +86,50 @@ public class ModuleSyncWatchdog { } /** - * Find any failed (locked) cm handles and change state back to 'ADVISED'. + * Populate work queue with advised cm handles from db. + * This method is made public for (integration) testing purposes. + * So it can be tested without the queue being emptied immediately as the main public method does. */ - @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:15000}") - public void resetPreviouslyFailedCmHandles() { - log.info("Processing module sync retry-watchdog waking up."); - final Collection<YangModelCmHandle> failedCmHandles - = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); - log.info("Retrying {} cmHandles", failedCmHandles.size()); - moduleSyncTasks.resetFailedCmHandles(failedCmHandles); + public void populateWorkQueueIfNeeded() { + if (moduleSyncWorkQueue.isEmpty() && workQueueLock.tryLock()) { + try { + populateWorkQueue(); + if (moduleSyncWorkQueue.isEmpty()) { + setPreviouslyLockedCmHandlesToAdvised(); + } + } finally { + workQueueLock.unlock(); + } + } } - private void preventBusyWait() { - try { - log.info("Busy waiting now"); - TimeUnit.MILLISECONDS.sleep(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); + private void populateWorkQueue() { + final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); + if (advisedCmHandles.isEmpty()) { + log.debug("No advised CM handles found in DB."); + } else { + log.info("Fetched {} advised CM handles from DB. Adding them to the work queue.", advisedCmHandles.size()); + advisedCmHandles.forEach(advisedCmHandle -> { + final String cmHandleId = String.valueOf(advisedCmHandle.getLeaves().get("id")); + if (moduleSyncWorkQueue.offer(advisedCmHandle)) { + log.info("CM handle {} added to the work queue.", cmHandleId); + } else { + log.warn("Failed to add CM handle {} to the work queue.", cmHandleId); + } + }); + log.info("Work queue contains {} items.", moduleSyncWorkQueue.size()); } } - private void populateWorkQueueIfNeeded() { - if (moduleSyncWorkQueue.isEmpty()) { - final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); - log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size()); - for (final DataNode advisedCmHandle : advisedCmHandles) { - if (!moduleSyncWorkQueue.offer(advisedCmHandle)) { - log.warn("Unable to add cm handle {} to the work queue", advisedCmHandle.getLeaves().get("id")); - } - } - log.info("Work Queue Size : {}", moduleSyncWorkQueue.size()); + private void setPreviouslyLockedCmHandlesToAdvised() { + final Collection<YangModelCmHandle> lockedCmHandles + = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); + if (lockedCmHandles.isEmpty()) { + log.debug("No locked CM handles found in DB."); + } else { + log.info("Found {} Locked CM Handles. Changing state to Advise to retry syncing them again.", + lockedCmHandles.size()); + moduleSyncTasks.setCmHandlesToAdvised(lockedCmHandles); } } @@ -130,8 +150,16 @@ public class ModuleSyncWatchdog { nextBatch.add(batchCandidate); } } - log.debug("nextBatch size : {}", nextBatch.size()); + log.info("nextBatch size : {}", nextBatch.size()); return nextBatch; } + private void preventBusyWait() { + try { + log.debug("Busy waiting now"); + sleeper.haveALittleRest(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java index 8ef98bc32a..1f33cc349d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java @@ -24,6 +24,7 @@ import com.hazelcast.config.MapConfig; import com.hazelcast.config.QueueConfig; import com.hazelcast.map.IMap; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.locks.Lock; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.cache.HazelcastCacheConfig; import org.onap.cps.spi.model.DataNode; @@ -43,6 +44,7 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { private static final QueueConfig commonQueueConfig = createQueueConfig("defaultQueueConfig"); private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig"); private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig"); + private static final String LOCK_NAME_FOR_WORK_QUEUE = "workQueueLock"; /** * Module Sync Distributed Queue Instance. @@ -51,7 +53,7 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { */ @Bean public BlockingQueue<DataNode> moduleSyncWorkQueue() { - return createHazelcastInstance("moduleSyncWorkQueue", commonQueueConfig).getQueue("moduleSyncWorkQueue"); + return getOrCreateHazelcastInstance(commonQueueConfig).getQueue("moduleSyncWorkQueue"); } /** @@ -61,7 +63,7 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { */ @Bean public IMap<String, Object> moduleSyncStartedOnCmHandles() { - return createHazelcastInstance("moduleSyncStartedOnCmHandles", moduleSyncStartedConfig).getMap( + return getOrCreateHazelcastInstance(moduleSyncStartedConfig).getMap( "moduleSyncStartedOnCmHandles"); } @@ -72,6 +74,23 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { */ @Bean public IMap<String, Boolean> dataSyncSemaphores() { - return createHazelcastInstance("dataSyncSemaphores", dataSyncSemaphoresConfig).getMap("dataSyncSemaphores"); + return getOrCreateHazelcastInstance(dataSyncSemaphoresConfig).getMap("dataSyncSemaphores"); + } + + /** + * Retrieves a distributed lock used to control access to the work queue for module synchronization. + * This lock ensures that the population and modification of the work queue are thread-safe and + * protected from concurrent access across different nodes in the distributed system. + * The lock guarantees that only one instance of the application can populate or modify the + * module sync work queue at a time, preventing race conditions and potential data inconsistencies. + * The lock is obtained using the Hazelcast CP Subsystem's {@link Lock}, which provides + * strong consistency guarantees for distributed operations. + * + * @return a {@link Lock} instance used for synchronizing access to the work queue. + */ + @Bean + public Lock workQueueLock() { + // TODO Method below does not use commonQueueConfig for creating lock (Refactor later) + return getOrCreateHazelcastInstance(commonQueueConfig).getCPSubsystem().getLock(LOCK_NAME_FOR_WORK_QUEUE); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventType.java index 4bc2f10218..1d4e3c8363 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventType.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventType.java @@ -26,10 +26,10 @@ public enum LcmEventType { private final String eventName; - private final String eventTypeTemplate = "org.onap.ncmp.cmhandle-lcm-event.%s"; + private static final String EVENT_TYPE_TEMPLATE = "org.onap.ncmp.cmhandle-lcm-event.%s"; LcmEventType(final String eventName) { - this.eventName = String.format(eventTypeTemplate, eventName); + this.eventName = String.format(EVENT_TYPE_TEMPLATE, eventName); } public String getEventType() { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java index 6cce153269..de3df6b9da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,14 +32,6 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; public interface LcmEventsCmHandleStateHandler { /** - * Updates the composite state of cmHandle based on cmHandleState. - * - * @param yangModelCmHandle cm handle represented as yang model - * @param targetCmHandleState target cm handle state - */ - void updateCmHandleState(final YangModelCmHandle yangModelCmHandle, final CmHandleState targetCmHandleState); - - /** * Updates the composite state of cmHandle based on cmHandleState in batch. * * @param cmHandleStatePerCmHandle Map of Yang Model Cm Handle and corresponding cm handle state. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java index cf7921c350..a53c902683 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -38,25 +39,12 @@ public class LcmEventsCmHandleStateHandlerAsyncHelper { private final LcmEventsService lcmEventsService; /** - * Publish LCM Event in asynchronous manner. - * - * @param targetNcmpServiceCmHandle target NcmpServiceCmHandle - * @param currentNcmpServiceCmHandle current NcmpServiceCmHandle - */ - @Async("notificationExecutor") - public void publishLcmEventAsynchronously(final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final NcmpServiceCmHandle currentNcmpServiceCmHandle) { - publishLcmEvent(targetNcmpServiceCmHandle, currentNcmpServiceCmHandle); - } - - /** * Publish LcmEvent in batches and in asynchronous manner. * * @param cmHandleTransitionPairs Pair of existing and modified cm handle represented as YangModelCmHandle */ @Async("notificationExecutor") - public void publishLcmEventBatchAsynchronously( - final Collection<LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair> cmHandleTransitionPairs) { + public void publishLcmEventBatchAsynchronously(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> publishLcmEvent( toNcmpServiceCmHandle(cmHandleTransitionPair.getTargetYangModelCmHandle()), toNcmpServiceCmHandle(cmHandleTransitionPair.getCurrentYangModelCmHandle()))); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java index b1b7b955f7..e9bd37219a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java @@ -38,12 +38,10 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CompositeState; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.impl.inventory.CompositeStateUtils; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; -import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.stereotype.Service; @Slf4j @@ -55,25 +53,6 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState private final LcmEventsCmHandleStateHandlerAsyncHelper lcmEventsCmHandleStateHandlerAsyncHelper; @Override - public void updateCmHandleState(final YangModelCmHandle updatedYangModelCmHandle, - final CmHandleState targetCmHandleState) { - - final CompositeState compositeState = updatedYangModelCmHandle.getCompositeState(); - - if (isCompositeStateSame(compositeState, targetCmHandleState)) { - log.debug("CmHandle with id : {} already in state : {}", updatedYangModelCmHandle.getId(), - targetCmHandleState); - } else { - final YangModelCmHandle currentYangModelCmHandle = YangModelCmHandle.deepCopyOf(updatedYangModelCmHandle); - updateToSpecifiedCmHandleState(updatedYangModelCmHandle, targetCmHandleState); - persistCmHandle(updatedYangModelCmHandle, currentYangModelCmHandle); - lcmEventsCmHandleStateHandlerAsyncHelper.publishLcmEventAsynchronously( - toNcmpServiceCmHandle(updatedYangModelCmHandle), - toNcmpServiceCmHandle(currentYangModelCmHandle)); - } - } - - @Override @Timed(value = "cps.ncmp.cmhandle.state.update.batch", description = "Time taken to update a batch of cm handle states") public void updateCmHandleStateBatch(final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle) { @@ -113,28 +92,13 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return cmHandleTransitionPairs; } - - private void persistCmHandle(final YangModelCmHandle targetYangModelCmHandle, - final YangModelCmHandle currentYangModelCmHandle) { - if (isNew(currentYangModelCmHandle.getCompositeState())) { - log.debug("Registering a new cm handle {}", targetYangModelCmHandle.getId()); - inventoryPersistence.saveCmHandle(targetYangModelCmHandle); - } else if (isDeleted(targetYangModelCmHandle.getCompositeState())) { - log.info("CmHandle with Id : {} is DELETED", targetYangModelCmHandle.getId()); - } else { - inventoryPersistence.saveCmHandleState(targetYangModelCmHandle.getId(), - targetYangModelCmHandle.getCompositeState()); - } - } - private void persistCmHandleBatch(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { final List<YangModelCmHandle> newCmHandles = new ArrayList<>(); final Map<String, CompositeState> compositeStatePerCmHandleId = new LinkedHashMap<>(); cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> { - if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState() - )) { + if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState())) { newCmHandles.add(cmHandleTransitionPair.getTargetYangModelCmHandle()); } else if (!isDeleted(cmHandleTransitionPair.getTargetYangModelCmHandle().getCompositeState())) { compositeStatePerCmHandleId.put(cmHandleTransitionPair.getTargetYangModelCmHandle().getId(), @@ -145,10 +109,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState inventoryPersistence.saveCmHandleBatch(newCmHandles); inventoryPersistence.saveCmHandleStateBatch(compositeStatePerCmHandleId); + logCmHandleStateChanges(cmHandleTransitionPairs); } private void updateToSpecifiedCmHandleState(final YangModelCmHandle yangModelCmHandle, - final CmHandleState targetCmHandleState) { + final CmHandleState targetCmHandleState) { if (READY == targetCmHandleState) { setInitialStates(yangModelCmHandle); @@ -193,8 +158,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return (compositeState != null && compositeState.getCmHandleState() == targetCmHandleState); } - private NcmpServiceCmHandle toNcmpServiceCmHandle(final YangModelCmHandle yangModelCmHandle) { - return YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle); + private static void logCmHandleStateChanges(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { + cmHandleTransitionPairs.stream() + .map(CmHandleTransitionPair::getTargetYangModelCmHandle) + .forEach(yangModelCmHandle -> log.info("{} is now in {} state", yangModelCmHandle.getId(), + yangModelCmHandle.getCompositeState().getCmHandleState().name())); } @Getter diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java index 7581c4af7a..aca485f53d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java @@ -66,7 +66,7 @@ public class DmiPluginTrustLevelWatchDog { log.debug("The Dmi Plugin: {} has already the same trust level: {}", dmiServiceName, newDmiTrustLevel); } else { final Collection<String> cmHandleIds = - cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiServiceName); + cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiServiceName, false); trustLevelManager.updateDmi(dmiServiceName, cmHandleIds, newDmiTrustLevel); } }); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java index 0e9eaf5090..06ca67e57e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java @@ -46,8 +46,7 @@ public class TrustLevelCacheConfig extends HazelcastCacheConfig { */ @Bean(TRUST_LEVEL_PER_CM_HANDLE) public Map<String, TrustLevel> trustLevelPerCmHandle() { - return createHazelcastInstance("hazelcastInstanceTrustLevelPerCmHandleMap", - trustLevelPerCmHandleCacheConfig).getMap(TRUST_LEVEL_PER_CM_HANDLE); + return getOrCreateHazelcastInstance(trustLevelPerCmHandleCacheConfig).getMap(TRUST_LEVEL_PER_CM_HANDLE); } /** @@ -57,7 +56,7 @@ public class TrustLevelCacheConfig extends HazelcastCacheConfig { */ @Bean(TRUST_LEVEL_PER_DMI_PLUGIN) public Map<String, TrustLevel> trustLevelPerDmiPlugin() { - return createHazelcastInstance("hazelcastInstanceTrustLevelPerDmiPluginMap", + return getOrCreateHazelcastInstance( trustLevelPerDmiPluginCacheConfig).getMap(TRUST_LEVEL_PER_DMI_PLUGIN); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java index afe6ad5c1c..9b0cc3a0c0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java @@ -140,7 +140,7 @@ public class TrustLevelManager { */ public TrustLevel getEffectiveTrustLevel(final String cmHandleId) { final TrustLevel dmiTrustLevel = TrustLevel.COMPLETE; // TODO: CPS-2375 - final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.get(cmHandleId); + final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.getOrDefault(cmHandleId, TrustLevel.NONE); return dmiTrustLevel.getEffectiveTrustLevel(cmHandleTrustLevel); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java index c526dfb297..9facd630a2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java @@ -38,14 +38,15 @@ public class AlternateIdMatcher { /** * Get data node that matches longest alternate id by removing elements (as defined by the separator string) - * from right to left. + * from right to left. If alternate id contains a hash then all elements after that hash are ignored. * * @param alternateId alternate ID * @param separator a string that separates each element from the next. * @return data node */ public DataNode getCmHandleDataNodeByLongestMatchingAlternateId(final String alternateId, final String separator) { - String bestMatch = alternateId; + final String[] splitPath = alternateId.split("#", 2); + String bestMatch = splitPath[0]; while (StringUtils.isNotEmpty(bestMatch)) { try { return inventoryPersistence.getCmHandleDataNodeByAlternateId(bestMatch); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java new file mode 100644 index 0000000000..7a02fa06e0 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.impl.utils; + +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Service; + +/** + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get to 100% code coverage. + */ +@Service +public class Sleeper { + public void haveALittleRest(final long timeInMillis) throws InterruptedException { + TimeUnit.MILLISECONDS.sleep(timeInMillis); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java index d8e8350345..eefabd1079 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.impl.utils.http; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.channel.ChannelOption; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; @@ -58,12 +57,11 @@ public class WebClientConfiguration { .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serviceConfig.getConnectionTimeoutInSeconds() * 1000) .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler( serviceConfig.getReadTimeoutInSeconds(), TimeUnit.SECONDS)).addHandlerLast( - new WriteTimeoutHandler(serviceConfig.getWriteTimeoutInSeconds(), TimeUnit.SECONDS))) + new WriteTimeoutHandler(serviceConfig.getWriteTimeoutInSeconds(), TimeUnit.SECONDS))) .resolver(DefaultAddressResolverGroup.INSTANCE) .compress(true); } - @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") private static ConnectionProvider getConnectionProvider(final ServiceConfig serviceConfig) { return ConnectionProvider.builder(serviceConfig.getConnectionProviderName()) .maxConnections(serviceConfig.getMaximumConnectionsTotal()) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy index e7eb893b03..0bd838437d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy @@ -29,29 +29,24 @@ class HazelcastCacheConfigSpec extends Specification { def objectUnderTest = new HazelcastCacheConfig() def 'Create Hazelcast instance with a #scenario'() { - given: 'a cluster name' + given: 'a cluster name and instance config name' objectUnderTest.clusterName = 'my cluster' - when: 'an hazelcast instance is created (name has to be unique)' - def result = objectUnderTest.createHazelcastInstance(scenario, config) + objectUnderTest.instanceConfigName = 'my instance config' + when: 'a hazelcast instance is created (name has to be unique)' + def result = objectUnderTest.getOrCreateHazelcastInstance(config) then: 'the instance is created and has the correct name' - assert result.name == scenario + assert result.name == 'my instance config' and: 'if applicable it has a map config with the expected name' if (expectMapConfig) { assert result.config.mapConfigs.values()[0].name == 'my map config' - } else { - assert result.config.mapConfigs.isEmpty() } and: 'if applicable it has a queue config with the expected name' if (expectQueueConfig) { assert result.config.queueConfigs.values()[0].name == 'my queue config' - } else { - assert result.config.queueConfigs.isEmpty() } and: 'if applicable it has a set config with the expected name' if (expectSetConfig) { assert result.config.setConfigs.values()[0].name == 'my set config' - } else { - assert result.config.setConfigs.isEmpty() } where: 'the following configs are used' scenario | config || expectMapConfig | expectQueueConfig | expectSetConfig diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfigSpec.groovy index 915bccb53d..740567c4bf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfigSpec.groovy @@ -36,13 +36,17 @@ class CmSubscriptionConfigSpec extends Specification { @Autowired IMap<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache; + def cleanupSpec() { + Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown() + } + def 'Embedded (hazelcast) cache for Cm Notification Subscription Cache.'() { expect: 'system is able to create an instance of the Cm Notification Subscription Cache' assert null != cmNotificationSubscriptionCache and: 'there is at least 1 instance' assert Hazelcast.allHazelcastInstances.size() > 0 and: 'Cm Notification Subscription Cache is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCmNotificationSubscription') + assert Hazelcast.allHazelcastInstances.name.contains('cps-and-ncmp-hazelcast-instance-test-config') } def 'Provided CM Subscription data'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumerSpec.groovy index 06651be913..ad5f42ed94 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumerSpec.groovy @@ -64,6 +64,7 @@ class CmAvcEventConsumerSpec extends MessagingBaseSpec { cloudEventKafkaConsumer.subscribe([cmEventsTopicName] as List<String>) and: 'an event is sent' def jsonData = TestUtils.getResourceFileContent('sampleAvcInputEvent.json') + def testEventKey = 'sample-eventid-key' def testEventSent = jsonObjectMapper.convertJsonString(jsonData, AvcEvent.class) def testCloudEventSent = CloudEventBuilder.v1() .withData(jsonObjectMapper.asJsonBytes(testEventSent)) @@ -72,17 +73,19 @@ class CmAvcEventConsumerSpec extends MessagingBaseSpec { .withSource(URI.create('sample-test-source')) .withExtension('correlationid', 'test-cmhandle1').build() and: 'event has header information' - def consumerRecord = new ConsumerRecord<String, CloudEvent>(cmEventsTopicName, 0, 0, 'sample-eventid', testCloudEventSent) - when: 'the event is consumed' + def consumerRecord = new ConsumerRecord<String, CloudEvent>(cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent) + when: 'the event is consumed and forwarded to target topic' acvEventConsumer.consumeAndForward(consumerRecord) - and: 'the topic is polled' + and: 'the target topic is polled' def records = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)) then: 'poll returns one record' assert records.size() == 1 - and: 'record can be converted to AVC event' + and: 'target record can be converted to AVC event' def record = records.iterator().next() def cloudEvent = record.value() as CloudEvent def convertedAvcEvent = toTargetEvent(cloudEvent, AvcEvent.class) + and: 'the target event has the same key as the source event to maintain the ordering in a partition' + assert record.key() == consumerRecord.key() and: 'we have correct headers forwarded where correlation id matches' assert KafkaHeaders.getParsedKafkaHeader(record.headers(), 'ce_correlationid') == 'test-cmhandle1' and: 'event id is same between consumed and forwarded' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy index fd76abb581..b046c12387 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -112,7 +112,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) - dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] + dataOperationRequest.dataOperationDefinitions[0].cmHandleReferences = [cmHandleId] and: 'a positive response from DMI service when it is called with valid request parameters' def responseFromDmi = Mono.just(new ResponseEntity<Object>(HttpStatus.ACCEPTED)) def expectedUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/data?requestId={requestId}&topic={topic}', ['requestId': 'requestId', 'topic': 'my-topic-name']) @@ -129,7 +129,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) - dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] + dataOperationRequest.dataOperationDefinitions[0].cmHandleReferences = [cmHandleId] and: 'the published cloud event will be captured' def actualDataOperationCloudEvent = null eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } @@ -143,7 +143,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { assert eventDataValue.statusMessage == UNKNOWN_ERROR.message and: 'the event contains the correct operation details' assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId - assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds + assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleReferences } def 'call get all resource data.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy index d5db24cc33..d5f705f2fd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy @@ -73,7 +73,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { and: 'notification feature is turned on/off' objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled when: 'data operation request is executed' - def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch']) + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleReferences: ['ch']) def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) then: 'the task is executed in an async fashion or not' expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) @@ -120,7 +120,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification { given: 'a data operation definition with too many cm handles' def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1 def cmHandleIds = new String[tooMany] - def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds) + def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleReferences: cmHandleIds) when: 'data operation request is executed' objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) then: 'a payload too large exception is thrown' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy index 5f83ad5f83..c62e93c214 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy @@ -42,9 +42,8 @@ class NetworkCmProxyFacadeSpec extends Specification { def mockDmiDataOperations = Mock(DmiDataOperations) def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) - def mockAlternateIdMatcher = Mock(AlternateIdMatcher) - def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations, mockAlternateIdMatcher) + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) def NO_TOPIC = null @@ -89,7 +88,6 @@ class NetworkCmProxyFacadeSpec extends Specification { given: 'a cm resource address for datastore operational' def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') and: 'get resource data from DMI is called' - mockAlternateIdMatcher.getCmHandleId('some CM Handle') >> 'some CM Handle' mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> Mono.just('dmi response') when: 'get resource data operational for the given cm resource address is called' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy index 46c0ddeb93..33dcf5d623 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy @@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.WebClientResponseException import reactor.core.publisher.Mono import spock.lang.Specification @@ -59,6 +60,8 @@ class PolicyExecutorSpec extends Specification { def setup() { setupLogger() objectUnderTest.enabled = true + objectUnderTest.serverAddress = 'some host' + objectUnderTest.serverPort = 'some port' mockWebClient.post() >> mockRequestBodyUriSpec mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec @@ -95,8 +98,8 @@ class PolicyExecutorSpec extends Specification { } def 'Permission check with non-2xx response and "allow" default decision.'() { - given: 'other http response' - mockResponse([], HttpStatus.I_AM_A_TEAPOT) + given: 'non-2xx http response' + mockErrorResponse() and: 'the configured default decision is "allow"' objectUnderTest.defaultDecision = 'allow' when: 'permission is checked for an operation' @@ -106,8 +109,8 @@ class PolicyExecutorSpec extends Specification { } def 'Permission check with non-2xx response and "other" default decision.'() { - given: 'other http response' - mockResponse([], HttpStatus.I_AM_A_TEAPOT) + given: 'non-2xx http response' + def webClientException = mockErrorResponse() and: 'the configured default decision is NOT "allow"' objectUnderTest.defaultDecision = 'deny by default' when: 'permission is checked for an operation' @@ -116,25 +119,23 @@ class PolicyExecutorSpec extends Specification { def thrownException = thrown(PolicyExecutorException) assert thrownException.message == 'Operation not allowed. Decision id N/A : deny by default' assert thrownException.details == 'Policy Executor returned HTTP Status code 418. Falling back to configured default decision: deny by default' + and: 'the cause is the original web client exception' + assert thrownException.cause == webClientException } def 'Permission check with invalid response from Policy Executor.'() { given: 'invalid response from Policy executor' - mockResponseSpec.toEntity(*_) >> invalidResponse + mockResponseSpec.toEntity(*_) >> Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) then: 'system logs the expected message' - assert getLogEntry(1) == expectedMessage - where: 'following invalid responses are received' - invalidResponse || expectedMessage - Mono.empty() || 'No valid response from Policy Executor, ignored' - Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) || 'No valid response body from Policy Executor, ignored' + assert getLogEntry(1) == 'No valid response body from Policy Executor, ignored' } def 'Permission check with timeout exception.'() { given: 'a timeout during the request' - def cause = new TimeoutException() - mockResponseSpec.toEntity(*_) >> { throw new RuntimeException(cause) } + def timeoutException = new TimeoutException() + mockResponseSpec.toEntity(*_) >> { throw new RuntimeException(timeoutException) } and: 'the configured default decision is NOT "allow"' objectUnderTest.defaultDecision = 'deny by default' when: 'permission is checked for an operation' @@ -143,6 +144,39 @@ class PolicyExecutorSpec extends Specification { def thrownException = thrown(PolicyExecutorException) assert thrownException.message == 'Operation not allowed. Decision id N/A : deny by default' assert thrownException.details == 'Policy Executor request timed out. Falling back to configured default decision: deny by default' + and: 'the cause is the original time out exception' + assert thrownException.cause == timeoutException + } + + def 'Permission check with unknown host.'() { + given: 'a unknown host exception during the request' + def unknownHostException = new UnknownHostException() + mockResponseSpec.toEntity(*_) >> { throw new RuntimeException(unknownHostException) } + and: 'the configured default decision is NOT "allow"' + objectUnderTest.defaultDecision = 'deny by default' + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) + then: 'Policy Executor exception is thrown' + def thrownException = thrown(PolicyExecutorException) + assert thrownException.message == 'Operation not allowed. Decision id N/A : deny by default' + assert thrownException.details == 'Cannot connect to Policy Executor (some host:some port). Falling back to configured default decision: deny by default' + and: 'the cause is the original unknown host exception' + assert thrownException.cause == unknownHostException + } + + def 'Permission check with #scenario exception and default decision "allow".'() { + given: 'a #scenario exception during the request' + mockResponseSpec.toEntity(*_) >> { throw new RuntimeException(cause)} + and: 'the configured default decision is "allow"' + objectUnderTest.defaultDecision = 'allow' + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) + then: 'no exception is thrown' + noExceptionThrown() + where: 'the following exceptions are thrown during the request' + scenario | cause + 'timeout' | new TimeoutException() + 'unknown host' | new UnknownHostException() } def 'Permission check with other runtime exception.'() { @@ -180,6 +214,13 @@ class PolicyExecutorSpec extends Specification { mockResponseSpec.toEntity(*_) >> mono } + def mockErrorResponse() { + def webClientResponseException = Mock(WebClientResponseException) + webClientResponseException.getStatusCode() >> HttpStatus.I_AM_A_TEAPOT + mockResponseSpec.toEntity(*_) >> { throw webClientResponseException } + return webClientResponseException + } + def setupLogger() { def logger = LoggerFactory.getLogger(PolicyExecutor) logger.setLevel(Level.TRACE) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy index 84eafb0da3..77e2c4fa25 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy @@ -74,15 +74,16 @@ class DmiDataOperationsHelperSpec extends MessagingBaseSpec { assert dmiDataOperationRequestBodyAsJsonNode.get('operationId').asText() == expectedOperationId assert dmiDataOperationRequestBodyAsJsonNode.get('datastore').asText() == expectedDatastore and: 'the correct cm handles (just for #serviceName)' - assert dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleIds.size() - expectedCmHandleIds.each { + assert dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleReferences.size() + expectedCmHandleReferences.each { dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').toString().contains(it) } where: 'the following dmi service and operations are checked' - serviceName | operationIndex || expectedOperationId | expectedDatastore | expectedCmHandleIds + serviceName | operationIndex || expectedOperationId | expectedDatastore | expectedCmHandleReferences 'dmi1' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] 'dmi1' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch1-dmi1', 'ch2-dmi1'] 'dmi1' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] + 'dmi1' | 3 || 'operational-16' | 'ncmp-datastore:passthrough-operational' | ['alt6-dmi1'] 'dmi2' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch3-dmi2'] 'dmi2' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch7-dmi2'] 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] @@ -137,14 +138,14 @@ class DmiDataOperationsHelperSpec extends MessagingBaseSpec { def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build() - return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) + return [new YangModelCmHandle(id: 'ch1-dmi1', 'alternateId': 'alt1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch2-dmi1', 'alternateId': 'alt2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch6-dmi1', 'alternateId': 'alt6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch8-dmi1', 'alternateId': 'alt8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch3-dmi2', 'alternateId': 'alt3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch4-dmi2', 'alternateId': 'alt4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch7-dmi2', 'alternateId': 'alt7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'non-ready-cm-handle', 'alternateId': 'non-ready-alternate', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) ] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy index 84eb78b751..47b57669ca 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy @@ -3,6 +3,8 @@ package org.onap.cps.ncmp.impl.datajobs import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest import org.onap.cps.ncmp.api.datajobs.models.WriteOperation +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -60,4 +62,18 @@ class WriteRequestExaminerSpec extends Specification { then: 'we get the operation ids in the expected order.' assert dmiWriteOperations.operationId == ['1', '2', '3'] } + + def 'Validate the creation of a ProducerKey with correct dmiservicename.'() { + given: 'yangModelCmHandles with service name: "#dmiServiceName" and data service name: "#dataServiceName"' + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dataServiceName, '', new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1'), '', '', 'dpi1') + when: 'the ProducerKey is created' + def result = objectUnderTest.createProducerKey(yangModelCmHandle).toString() + then: 'we get the ProducerKey with the correct service name' + assert result == expectedProducerKey + where: 'the following services are registered' + dmiServiceName | dataServiceName || expectedProducerKey + 'dmi-service-name' | '' || 'dmi-service-name#dpi1' + '' | 'dmi-data-service-name' || 'dmi-data-service-name#dpi1' + 'dmi-service-name' | 'dmi-data-service-name' || 'dmi-service-name#dpi1' + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy index 65fda8718c..d00d3ab8f6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy @@ -61,7 +61,7 @@ abstract class DmiOperationsBaseSpec extends Specification { def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { populateYangModelCmHandle(dmiProperties, '') - mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] + mockInventoryPersistence.getYangModelCmHandlesFromCmHandleReferences(_) >> [yangModelCmHandle] } def populateYangModelCmHandle(dmiProperties, moduleSetTag) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 7e34fe2822..ce08156fec 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -64,16 +64,16 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() when: 'a query on cmhandle public properties is performed with a public property pair' - def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) + def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs, outputAlternateId) then: 'the correct cm handle data objects are returned' - result.containsAll(expectedCmHandleIds) - result.size() == expectedCmHandleIds.size() + result.containsAll(expectedCmHandleReferences) + result.size() == expectedCmHandleReferences.size() where: 'the following data is used' - scenario | publicPropertyPairs || expectedCmHandleIds - 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property does not match' | [wont_match: 'wont_match'] || [] - '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] - '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] + scenario | publicPropertyPairs | outputAlternateId || expectedCmHandleReferences + 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] | false || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property does not match' | [wont_match: 'wont_match'] | false || [] + '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] | true || ['alt-PNFDemo4'] + '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] | false || [] } def 'Query cm handles on trust level'() { @@ -84,22 +84,26 @@ class CmHandleQueryServiceImplSpec extends Specification { and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' mockResponses() when: 'the query is run' - def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) - then: 'the result contain trusted PNFDemo' + def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId) + then: 'the result contain trusted cmHandle reference' assert result.size() == 1 - assert result[0] == 'PNFDemo' + assert result[0] == expectedCmHandleReference + where: 'the following data is used' + senario | outputAlternateId | expectedCmHandleReference + 'output cmHandleId' | false | 'PNFDemo' + 'output AlternateId' | true | 'alt-PNFDemo' } def 'Query CmHandles using empty public properties query pair.'() { when: 'a query on CmHandle public properties is executed using an empty map' - def result = objectUnderTest.queryCmHandlePublicProperties([:]) + def result = objectUnderTest.queryCmHandlePublicProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } def 'Query CmHandles using empty private properties query pair.'() { when: 'a query on CmHandle private properties is executed using an empty map' - def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) + def result = objectUnderTest.queryCmHandleAdditionalProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } @@ -108,7 +112,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a data node exists with a certain additional-property' mockCpsQueryService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] when: 'a query on CmHandle private properties is executed using a map' - def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) + def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'], false) then: 'one cm handle is returned' result.size() == 1 } @@ -195,15 +199,30 @@ class CmHandleQueryServiceImplSpec extends Specification { assert result.contains(cmHandleDataNode) } - def 'Get all cm handles by dmi plugin identifier'() { + def 'Get all cm handles by dmi plugin identifier and alternate id output option where #scenario'() { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() - when: 'cm Handles are fetched for a given dmi plugin identifier' - def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier') + when: 'cm Handles are fetched for a given dmi plugin identifier and alternate id output option' + def result = objectUnderTest.getCmHandleReferencesByDmiPluginIdentifier('my-dmi-plugin-identifier', outputAlternateId) then: 'result is the correct size' assert result.size() == 3 and: 'result contains the correct cm handles' - assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') + assert result.containsAll(expectedResult) + where: + scenario | outputAlternateId || expectedResult + 'output is for alternate ids' | true || ['alt-PNFDemo', 'alt-PNFDemo2', 'alt-PNFDemo4'] + 'output is for cm handle ids' | false || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + } + + def 'Get all alternateIds by dmi plugin identifier'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'cm Handles are fetched for a given dmi plugin identifier' + def result = objectUnderTest.getCmHandleReferencesMapByDmiPluginIdentifier('my-dmi-plugin-identifier').values() + then: 'result is the correct size' + assert result.size() == 3 + and: 'result contains the correct alternate Ids' + assert result.containsAll('alt-PNFDemo', 'alt-PNFDemo2', 'alt-PNFDemo4') } void mockResponses() { @@ -219,6 +238,6 @@ class CmHandleQueryServiceImplSpec extends Specification { } def static createDataNode(dataNodeId) { - return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) + return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId, 'alternate-id':'alt-' + dataNodeId]) } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy index 1beab20def..6213258303 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -162,9 +162,9 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { } where: scenario | cmHandleId | exception || expectedError | expectedErrorText - 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' + 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle reference(s) not found' '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' + 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm handle reference has an invalid character(s) in id' } def 'Multiple update operations in a single request'() { @@ -193,7 +193,7 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { assert it.status == Status.FAILURE assert it.cmHandle == cmHandleId assert it.ncmpResponseStatus == CM_HANDLES_NOT_FOUND - assert it.errorText == 'cm handle id(s) not found' + assert it.errorText == 'cm handle reference(s) not found' } then: 'the replace list method is called twice' 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index dcff2e9b89..a69721b6aa 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -87,7 +87,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm handle is in READY state' mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true when: 'registration is processed' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'cm-handles are removed first' 1 * objectUnderTest.processRemovedCmHandles(*_) and: 'de-registered cm handle entry is removed from in progress map' @@ -108,7 +108,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles[0].status == expectedResponseStatus where: 'the following parameters are used' @@ -124,7 +124,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode where: 'the following parameters are used' @@ -139,7 +139,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'update registration and sync module is called with correct DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'create cm handles registration and sync modules is called with the correct plugin information' 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) where: @@ -155,7 +155,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'registration is called with incorrect DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a DMI Request Exception is thrown with correct message details' def exceptionThrown = thrown(DmiRequestException.class) assert exceptionThrown.getMessage().contains(expectedMessageDetails) @@ -178,7 +178,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a successful response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -206,7 +206,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'trustLevel is set for the created cm-handle' 1 * mockTrustLevelManager.registerCmHandles(expectedMapping) where: @@ -225,7 +225,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def xpath = "somePathWithId[@id='cmhandle2']" mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } when: 'registration is updated to create cm-handles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a response is received for all cm-handles' response.createdCmHandles.size() == 1 and: 'all cm-handles creation fails' @@ -244,7 +244,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handler registration fails: #scenario' mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -269,7 +269,7 @@ class CmHandleRegistrationServiceSpec extends Specification { CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the response contains updateOperationResponse' assert response.updatedCmHandles.size() == 4 assert response.updatedCmHandles.containsAll(updateOperationResponse) @@ -281,7 +281,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: '#scenario' mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle state is updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> args[0].values()[0] == CmHandleState.DELETING } @@ -315,7 +315,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } when: 'registration is updated to delete cmhandles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle states are all updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) and: 'a response is received for all cm-handles' @@ -361,7 +361,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'schema set single deletion failed with unknown error' mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'no exception is thrown' noExceptionThrown() and: 'cm-handle is not deleted' @@ -387,7 +387,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion fails on individual delete' mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' assert response.removedCmHandles.size() == 1 with(response.removedCmHandles[0]) { @@ -399,10 +399,10 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'the cm handle state is not updated to "DELETED"' 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_, CmHandleState.DELETED) where: - scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText - 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' - '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' + scenario | deleteListElementException || expectedError | expectedErrorText + 'cm-handle does not exist' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle reference(s) not found' + 'cm-handle has invalid name' | new DataValidationException('', '') || CM_HANDLE_INVALID_ID | 'cm handle reference has an invalid character(s) in id' + 'an unexpected exception' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index 1830f1331d..ce4a449b21 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -28,11 +28,11 @@ import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference @@ -74,12 +74,16 @@ class InventoryPersistenceImplSpec extends Specification { .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) def cmHandleId = 'some-cm-handle' - def leaves = ["id":cmHandleId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] + def alternateId = 'some-alternate-id' + def leaves = ["id":cmHandleId, "alternateId":alternateId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" def cmHandleId2 = 'another-cm-handle' + def alternateId2 = 'another-alternate-id' def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']" + def dataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]) + @Shared def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]), new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] @@ -134,7 +138,7 @@ class InventoryPersistenceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters(cmHandleId) } - def "Retrieve multiple YangModelCmHandles"() { + def "Retrieve multiple YangModelCmHandles using cm handle ids"() { given: 'the cps data service returns 2 data nodes from the DMI registry' def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])] mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes @@ -145,6 +149,17 @@ class InventoryPersistenceImplSpec extends Specification { assert results.id.containsAll([cmHandleId, cmHandleId2]) } + def "Retrieve multiple YangModelCmHandles using cm handle references"() { + given: 'the cps data service returns 2 data nodes from the DMI registry' + def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId, 'alternate-id':alternateId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2,'alternate-id':alternateId2])] + mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, INCLUDE_ALL_DESCENDANTS) >> dataNodes + when: 'retrieving the yang modelled cm handle' + def results = objectUnderTest.getYangModelCmHandlesFromCmHandleReferences([cmHandleId, cmHandleId2]) + then: 'verify both have returned and cmhandleIds are correct' + assert results.size() == 2 + assert results.id.containsAll([cmHandleId, cmHandleId2]) + } + def 'Get a Cm Handle Composite State'() { given: 'a valid cm handle id' def cmHandleId = 'Some-Cm-Handle' @@ -312,17 +327,9 @@ class InventoryPersistenceImplSpec extends Specification { when: 'getting the cm handle data node' objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') then: 'no data found exception thrown' - def thrownException = thrown(DataNodeNotFoundException) - assert thrownException.getMessage().contains('DataNode not found') - } - - def 'Get multiple cm handle data nodes by alternate ids'() { - given: 'expected xPath to get cmHandle data node' - def expectedXPath = "/dmi-registry/cm-handles[@alternate-id='A' or @alternate-id='B']" - when: 'getting the cm handle data node' - objectUnderTest.getCmHandleDataNodesByAlternateIds(['A', 'B']) - then: 'query service is invoked with expected xpath' - 1 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS) + def thrownException = thrown(CmHandleNotFoundException) + assert thrownException.getMessage().contains('Cm handle not found') + assert thrownException.getDetails().contains('No cm handles found with reference alternate id') } def 'Get multiple cm handle data nodes by alternate ids, passing empty collection'() { @@ -332,13 +339,25 @@ class InventoryPersistenceImplSpec extends Specification { 0 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, _) } - def 'Get CM handles that has given module names'() { + def 'Get CM handle ids for CM Handles that has given module names'() { when: 'the method to get cm handles is called' - objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) + objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false) then: 'the admin persistence service method to query anchors is invoked once with the same parameter' 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) } + def 'Get Alternate Ids for CM Handles that has given module names'() { + given: 'A Collection of data nodes' + def dataNodes = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])] + when: 'the methods to get dataNodes is called and returns correct values' + mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) >> ['ch-1'] + mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ["/dmi-registry/cm-handles[@id='ch-1']"], INCLUDE_ALL_DESCENDANTS) >> dataNodes + and: 'the method returns a result' + def result = objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], true) + then: 'the result contains the correct alternate Id' + assert result == ['alt-1'] as HashSet + } + def 'Replace list content'() { when: 'replace list content method is called with xpath and data nodes collection' objectUnderTest.replaceListContent('sample xpath', [new DataNode()]) @@ -359,4 +378,11 @@ class InventoryPersistenceImplSpec extends Specification { then: 'the cps data service method to delete data nodes is invoked once with the same xPaths' 1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP); } + + def 'Check if cm handle exists for a given cm handle id'() { + given: 'data service returns a datanode with correct cm handle id' + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] + expect: 'cm handle exists for given cm handle id' + assert true == objectUnderTest.isExistingCmHandleId('some-cm-handle') + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index fec07556eb..282bd9e89b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -56,12 +56,12 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { given: 'an (updated) dmi plugin registration' def dmiPluginRegistration = Mock(DmiPluginRegistration) when: 'the registration is submitted ' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the call is delegated to the cm handle registration service' - 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + 1 * mockCmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration) } - def 'Execute cm handle id search for inventory'() { + def 'Execute cm handle reference search for inventory'() { given: 'a ConditionApiProperties object' def conditionProperties = new ConditionProperties() conditionProperties.conditionName = 'hasAllProperties' @@ -71,22 +71,27 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { and: 'the system returns an set of cmHandle ids' mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] when: 'executing the search' - def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, false) then: 'the result returns the correct 2 elements' assert result.size() == 2 assert result.contains('cmHandle1') assert result.contains('cmHandle2') } - def 'Get all cm handle IDs by DMI plugin identifier.' () { - given: 'cm handle queries service returns cm handles' - 1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] - when: 'cm handle Ids are requested with dmi plugin identifier' - def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') + def 'Get all cm handle references by DMI plugin identifier and alternate id output option where #scenario.' () { + given: 'cm handle queries service returns cm handle references' + mockCmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', false) >> ['cm-handle-1','cm-handle-2'] + mockCmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', true) >> ['alternate-id-1','alternate-id-2'] + when: 'cm handle references are requested with dmi plugin identifier and alternate id output option' + def result = objectUnderTest.getAllCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', outputAlternateId) then: 'the result size is correct' assert result.size() == 2 and: 'the result returns the correct details' - assert result.containsAll('cm-handle-1','cm-handle-2') + assert result.containsAll(expectedResult) + where: + scenario | outputAlternateId || expectedResult + 'output is for alternate ids' | true || ['alternate-id-1', 'alternate-id-2'] + 'output is for cm handle ids' | false || ['cm-handle-1','cm-handle-2'] } def 'Getting Yang Resources for a given #scenario'() { @@ -191,7 +196,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Execute cm handle id search'() { + def 'Execute cm handle reference search'() { given: 'valid CmHandleQueryApiParameters input' def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() def conditionApiProperties = new ConditionApiProperties() @@ -199,11 +204,11 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] and: 'query cm handle method return with a data node list' - mockParameterizedCmHandleQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + mockParameterizedCmHandleQueryService.queryCmHandleReferenceIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class), false) >> ['cm-handle-id-1'] when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters, false) then: 'result is the same collection as returned by the CPS Data Service' assert result == ['cm-handle-id-1'] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 013bace04d..0c44196348 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,11 +50,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'output CmHandle Ids' | false || ['some-cmhandle-id'] as Set + 'output Alternate Ids' | true || ['some-alternate-id'] as Set } def 'Query cm handle where cps path itself is ancestor axis.'() { @@ -63,11 +67,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle data nodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'outputAlternate is false' | false || ['some-cmhandle-id'] as Set + 'outputAlternate is true' | true || ['some-alternate-id'] as Set } def 'Cm handle ids query with error: #scenario.'() { @@ -78,7 +86,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { and: 'cmHandleQueries throws a path parsing exception' cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } when: 'the query is executed for cm handle ids' - objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'a data validation exception is thrown' thrown(expectedException) where: 'the following data is used' @@ -93,7 +101,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'empty result is returned' assert result.isEmpty() } @@ -104,9 +112,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> cmHandleIdsFromService and: 'the correct expected cm handles ids are returned' assert result.size() == cmHandleIdsFromService.size() assert result.containsAll(cmHandleIdsFromService) @@ -122,9 +130,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) when: 'the query is being executed' - objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(trustLevelQueryParameters, false) then: 'the query is being delegated to the cm handle query service with correct parameter' - 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) + 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map, false) } def 'Query cm handle details with module names when #scenario from query.'() { @@ -135,7 +143,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { when: 'the query is executed for cm handle ids' def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> ['ch1'] and: 'the inventory service is called with teh correct if and returns a yang model cm handle' 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] @@ -145,15 +153,19 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { assert result[0].dmiProperties == [name:'value'] } - def 'Query cm handle ids when the query is empty.'() { + def 'Query cm handle references when the query is empty.'() { given: 'We use an empty query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() and: 'the inventory persistence returns the dmi registry datanode with just ids' mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] when: 'the query is executed for both cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles are returned' - assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + assert result.containsAll(expectedCmhandleReferences) + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReferences + 'outputAlternate is false' | false || ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] + 'outputAlternate is true' | true || ['alt-PNFDemo1', 'alt-PNFDemo2', 'alt-PNFDemo3', 'alt-PNFDemo4'] } def 'Query cm handle details when the query is empty.'() { @@ -177,7 +189,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false) then: 'the expected number of results are returned.' assert result.size() == expectedCmHandleIdsSize where: 'the following data is used' @@ -188,21 +200,31 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { 'additional properties, no matching cm handles' | 'hasAllAdditionalProperties' | null | [] || 0 } - def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () { + def 'Retrieve alternate ids by different DMI properties.' () { given: 'a query object created with dmi plugin as condition' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the inventoryPersistence returns different CmHandleIds' - partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult + partiallyMockedCmHandleQueries.getCmHandleReferencesMapByDmiPluginIdentifier(*_) >> [:] when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, true) then: 'the expected number of results are returned.' - assert result.size() == expectedCmHandleIdsSize - where: 'the following data is used' - scenario | cmHandleQueryResult || expectedCmHandleIdsSize - 'some matches' | ['h1','h2'] || 2 - 'no matches' | [] || 0 + assert result.size() == 0 + } + + def 'Retrieve cm handle ids by different DMI properties.' () { + given: 'a query object created with dmi plugin as condition' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the inventoryPersistence returns different CmHandleIds' + partiallyMockedCmHandleQueries.getCmHandleReferencesByDmiPluginIdentifier(_, _) >> ['h1','h2'] + when: 'the query executed' + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false) + then: 'the expected number of results are returned.' + assert result.size() == 2 + } def 'Combine two query results where #scenario.'() { @@ -227,7 +249,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def static createDataNodeList(dataNodeIds) { def dataNodes =[] - dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } + dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it, 'alternate-id':'alt-' + it]) } return dataNodes } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 160744a7d7..8ce1e934f2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -34,8 +34,10 @@ import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory +import spock.lang.Ignore import spock.lang.Specification import java.util.concurrent.atomic.AtomicInteger @@ -121,6 +123,26 @@ class ModuleSyncTasksSpec extends Specification { 'module upgrade' | MODULE_UPGRADE | 'Upgrade in progress' || MODULE_UPGRADE_FAILED } + @Ignore // TODO Enable this test once the bug CPS-2474 is fixed + def 'Module sync succeeds even if a handle gets deleted during module sync.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) + and: 'inventory persistence cannot find the first handle' + mockInventoryPersistence.getCmHandleState('cm-handle-1') >> { throw new DataNodeNotFoundException('dataspace', 'anchor', 'xpath') } + and: 'inventory persistence returns the second handle with ADVISED state' + mockInventoryPersistence.getCmHandleState('cm-handle-2') >> new CompositeState(cmHandleState: CmHandleState.ADVISED) + when: 'module sync poll is executed' + objectUnderTest.performModuleSync([cmHandle1, cmHandle2], batchCount) + then: 'no exception is thrown' + noExceptionThrown() + and: 'the deleted cm-handle did not sync' + 0 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } + and: 'the existing cm-handle synced' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } + and: 'the state handler called' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) + } def 'Reset failed CM Handles #scenario.'() { given: 'cm handles in an locked state' @@ -136,7 +158,7 @@ class ModuleSyncTasksSpec extends Specification { moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started') moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started') when: 'resetting failed cm handles' - objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) + objectUnderTest.setCmHandlesToAdvised([yangModelCmHandle1, yangModelCmHandle2]) then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) and: 'after reset performed progress map is empty' @@ -153,7 +175,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'module sync poll is executed' objectUnderTest.performModuleSync([cmHandle1], batchCount) then: 'module sync service is invoked for cm handle' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) and: 'the entry for other cm handle is still in the progress map' assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 155edc8bc6..f2c88a511e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -23,14 +23,16 @@ package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.utils.Sleeper import org.onap.cps.spi.model.DataNode import spock.lang.Specification import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.locks.Lock class ModuleSyncWatchdogSpec extends Specification { - def mockSyncUtils = Mock(ModuleOperationsUtils) + def mockModuleOperationsUtils = Mock(ModuleOperationsUtils) def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE @@ -42,15 +44,23 @@ class ModuleSyncWatchdogSpec extends Specification { def spiedAsyncTaskExecutor = Spy(AsyncTaskExecutor) - def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor) + def mockWorkQueueLock = Mock(Lock) + + def spiedSleeper = Spy(Sleeper) + + def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock, spiedSleeper) void setup() { spiedAsyncTaskExecutor.setupThreadPool() } def 'Module sync advised cm handles with #scenario.'() { - given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + given: 'module sync utilities returns #numberOfAdvisedCmHandles advised cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + and: 'module sync utilities returns no failed (locked) cm handles' + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> [] + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has enough available threads' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 when: ' module sync is started' @@ -59,6 +69,7 @@ class ModuleSyncWatchdogSpec extends Specification { expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) where: 'the following parameter are used' scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions + 'none at all' | 0 || 0 'less then 1 batch' | 1 || 1 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 @@ -66,9 +77,11 @@ class ModuleSyncWatchdogSpec extends Specification { 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 } - def 'Module sync advised cm handles starts with no available threads.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync cm handles starts with no available threads.'() { + given: 'module sync utilities returns a advise cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor first has no threads but has one thread on the second attempt' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] when: ' module sync is started' @@ -77,9 +90,11 @@ class ModuleSyncWatchdogSpec extends Specification { 1 * spiedAsyncTaskExecutor.executeTask(*_) } - def 'Module sync advised cm handles already handled.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync advised cm handle already handled by other thread.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has a thread available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 and: 'the semaphore cache indicates the cm handle is already being processed' @@ -94,7 +109,7 @@ class ModuleSyncWatchdogSpec extends Specification { given: 'there is still a cm handle in the queue' moduleSyncWorkQueue.offer(new DataNode()) and: 'sync utilities returns many advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(500) + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(500) and: 'the executor has plenty threads available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 10 when: ' module sync is started' @@ -104,18 +119,42 @@ class ModuleSyncWatchdogSpec extends Specification { } def 'Reset failed cm handles.'() { - given: 'sync utilities returns failed cm handles' + given: 'module sync utilities returns failed cm handles' def failedCmHandles = [new YangModelCmHandle()] - mockSyncUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles when: 'reset failed cm handles is started' - objectUnderTest.resetPreviouslyFailedCmHandles() + objectUnderTest.setPreviouslyLockedCmHandlesToAdvised() then: 'it is delegated to the module sync task (service)' - 1 * mockModuleSyncTasks.resetFailedCmHandles(failedCmHandles) + 1 * mockModuleSyncTasks.setCmHandlesToAdvised(failedCmHandles) + } + + def 'Module Sync Locking.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'can lock is : #canLock' + mockWorkQueueLock.tryLock() >> canLock + when: 'attempt to populate the work queue' + objectUnderTest.populateWorkQueueIfNeeded() + then: 'the queue remains empty is #expectQueueRemainsEmpty' + assert moduleSyncWorkQueue.isEmpty() == expectQueueRemainsEmpty + where: 'the following lock states are applied' + canLock | expectQueueRemainsEmpty + false | true + true | false + } + + def 'Sleeper gets interrupted.'() { + given: 'sleeper gets interrupted' + spiedSleeper.haveALittleRest(_) >> { throw new InterruptedException() } + when: 'the watchdog attempts to sleep to save cpu cycles' + objectUnderTest.preventBusyWait() + then: 'no exception is thrown' + noExceptionThrown() } def createDataNodes(numberOfDataNodes) { def dataNodes = [] - (1..numberOfDataNodes).each {dataNodes.add(new DataNode())} + numberOfDataNodes.times { dataNodes.add(new DataNode()) } return dataNodes } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy index 8e59922c41..4c96d6b822 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy @@ -46,6 +46,10 @@ class SynchronizationCacheConfigSpec extends Specification { @Autowired private IMap<String, Boolean> dataSyncSemaphores + def cleanupSpec() { + Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown() + } + def 'Embedded (hazelcast) Caches for Module and Data Sync.'() { expect: 'system is able to create an instance of the Module Sync Work Queue' assert null != moduleSyncWorkQueue @@ -53,22 +57,19 @@ class SynchronizationCacheConfigSpec extends Specification { assert null != moduleSyncStartedOnCmHandles and: 'system is able to create an instance of a map to hold data sync semaphores' assert null != dataSyncSemaphores - and: 'there are at least 3 instances' - assert Hazelcast.allHazelcastInstances.size() > 2 and: 'they have the correct names (in any order)' - assert Hazelcast.allHazelcastInstances.name.containsAll('moduleSyncWorkQueue', 'moduleSyncStartedOnCmHandles', 'dataSyncSemaphores') + assert Hazelcast.allHazelcastInstances.name.contains('cps-and-ncmp-hazelcast-instance-test-config') } def 'Verify configs for Distributed objects'(){ - given: 'the Module Sync Work Queue config' - def moduleSyncWorkQueueConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config - def moduleSyncDefaultWorkQueueConfig = moduleSyncWorkQueueConfig.queueConfigs.get('defaultQueueConfig') + given: 'hazelcast common config' + def hzConfig = Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').config + and: 'the Module Sync Work Queue config' + def moduleSyncDefaultWorkQueueConfig = hzConfig.queueConfigs.get('defaultQueueConfig') and: 'the Module Sync Started Cm Handle Map config' - def moduleSyncStartedOnCmHandlesConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config - def moduleSyncStartedOnCmHandlesMapConfig = moduleSyncStartedOnCmHandlesConfig.mapConfigs.get('moduleSyncStartedConfig') + def moduleSyncStartedOnCmHandlesMapConfig = hzConfig.mapConfigs.get('moduleSyncStartedConfig') and: 'the Data Sync Semaphores Map config' - def dataSyncSemaphoresConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config - def dataSyncSemaphoresMapConfig = dataSyncSemaphoresConfig.mapConfigs.get('dataSyncSemaphoresConfig') + def dataSyncSemaphoresMapConfig = hzConfig.mapConfigs.get('dataSyncSemaphoresConfig') expect: 'system created instance with correct config of Module Sync Work Queue' assert moduleSyncDefaultWorkQueueConfig.backupCount == 1 assert moduleSyncDefaultWorkQueueConfig.asyncBackupCount == 0 @@ -79,28 +80,15 @@ class SynchronizationCacheConfigSpec extends Specification { assert dataSyncSemaphoresMapConfig.backupCount == 1 assert dataSyncSemaphoresMapConfig.asyncBackupCount == 0 and: 'all instances are part of same cluster' - def testClusterName = 'cps-and-ncmp-test-caches' - assert moduleSyncWorkQueueConfig.clusterName == testClusterName - assert moduleSyncStartedOnCmHandlesConfig.clusterName == testClusterName - assert dataSyncSemaphoresConfig.clusterName == testClusterName + assert hzConfig.clusterName == 'cps-and-ncmp-test-caches' } def 'Verify deployment network configs for Distributed objects'() { - given: 'the Module Sync Work Queue config' - def queueNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config.networkConfig - and: 'the Module Sync Started Cm Handle Map config' - def moduleSyncStartedOnCmHandlesNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config.networkConfig - and: 'the Data Sync Semaphores Map config' - def dataSyncSemaphoresNetworkConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config.networkConfig - expect: 'system created instance with correct config of Module Sync Work Queue' - assert queueNetworkConfig.join.autoDetectionConfig.enabled - assert !queueNetworkConfig.join.kubernetesConfig.enabled - and: 'Module Sync Started Cm Handle Map has the correct settings' - assert moduleSyncStartedOnCmHandlesNetworkConfig.join.autoDetectionConfig.enabled - assert !moduleSyncStartedOnCmHandlesNetworkConfig.join.kubernetesConfig.enabled - and: 'Data Sync Semaphore Map has the correct settings' - assert dataSyncSemaphoresNetworkConfig.join.autoDetectionConfig.enabled - assert !dataSyncSemaphoresNetworkConfig.join.kubernetesConfig.enabled + given: 'common hazelcast network config' + def hzConfig = Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').config.networkConfig + and: 'all configs has the correct settings' + assert hzConfig.join.autoDetectionConfig.enabled + assert !hzConfig.join.kubernetesConfig.enabled } def 'Verify network config'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index bd7c321bc7..4b676e1b4c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,19 @@ package org.onap.cps.ncmp.impl.inventory.sync.lcm +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.slf4j.LoggerFactory import spock.lang.Specification +import static java.util.Collections.EMPTY_LIST +import static java.util.Collections.EMPTY_MAP import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING @@ -35,6 +42,17 @@ import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { + def logger = Spy(ListAppender<ILoggingEvent>) + + void setup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).addAppender(logger) + logger.start() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).detachAndStopAllAppenders() + } + def mockInventoryPersistence = Mock(InventoryPersistence) def mockLcmEventsCreator = Mock(LcmEventsCreator) def mockLcmEventsService = Mock(LcmEventsService) @@ -51,30 +69,39 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { compositeState = new CompositeState(cmHandleState: fromCmHandleState) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, toCmHandleState) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, toCmHandleState)) then: 'state is saved using inventory persistence' - expectedCallsToInventoryPersistence * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { + args -> { + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).cmHandleState == toCmHandleState + } + } + and: 'log message shows state change at INFO level' + def loggingEvent = (ILoggingEvent) logger.list[0] + assert loggingEvent.level == Level.INFO + assert loggingEvent.formattedMessage == "${cmHandleId} is now in ${toCmHandleState} state" and: 'event service is called to publish event' - expectedCallsToEventService * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) where: 'state change parameters are provided' - stateChange | fromCmHandleState | toCmHandleState || expectedCallsToInventoryPersistence | expectedCallsToEventService - 'ADVISED to READY' | ADVISED | READY || 1 | 1 - 'READY to LOCKED' | READY | LOCKED || 1 | 1 - 'ADVISED to ADVISED' | ADVISED | ADVISED || 0 | 0 - 'READY to READY' | READY | READY || 0 | 0 - 'LOCKED to LOCKED' | LOCKED | LOCKED || 0 | 0 - 'DELETED to ADVISED' | DELETED | ADVISED || 0 | 1 + stateChange | fromCmHandleState | toCmHandleState + 'ADVISED to READY' | ADVISED | READY + 'READY to LOCKED' | READY | LOCKED + 'ADVISED to LOCKED' | ADVISED | LOCKED + 'ADVISED to DELETING' | ADVISED | DELETING } - def 'Update and Publish Events on State Change from NO_EXISTING state to ADVISED'() { + def 'Update and Publish Events on State Change from non-existing to ADVISED'() { given: 'Cm Handle represented as YangModelCmHandle' yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: []) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'state is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) + then: 'CM-handle is saved using inventory persistence' + 1 * mockInventoryPersistence.saveCmHandleBatch(List.of(yangModelCmHandle)) and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } def 'Update and Publish Events on State Change from LOCKED to ADVISED'() { @@ -83,69 +110,62 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { lockReason: CompositeState.LockReason.builder().lockReasonCategory(MODULE_SYNC_FAILED).details('some lock details').build()) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) then: 'state is saved using inventory persistence and old lock reason details are retained' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[1] as CompositeState).lockReason.details == 'some lock details' + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).lockReason.details == 'some lock details' } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } - def 'Update and Publish Events on State Change from DELETING to ADVISED'() { - given: 'Cm Handle represented as YangModelCmHandle in DELETING state' - yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) - when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'the cm handle is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) - and: 'event service is called to publish event' - 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) - } - - def 'Update and Publish Events on State Change to READY'() { + def 'Update and Publish Events on State Change to from ADVISED to READY'() { given: 'Cm Handle represented as YangModelCmHandle' compositeState = new CompositeState(cmHandleState: ADVISED) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) and: 'global sync flag is set' compositeState.setDataSyncEnabled(false) when: 'update cmhandle state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, READY) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, READY)) then: 'state is saved using inventory persistence with expected dataSyncState' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args-> { - def result = (args[1] as CompositeState) - assert result.dataSyncEnabled == false - assert result.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED - + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataSyncEnabled == false + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in READY state" } - def 'Update cmHandle state to "DELETING"' (){ + def 'Update cmHandle state from READY to DELETING' (){ given: 'cm Handle as Yang model' compositeState = new CompositeState(cmHandleState: READY) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETING"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETING) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETING)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETING and: 'method to persist cm handle state is called once' - 1 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState()) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(Map.of(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState())) and: 'the method to publish Lcm event is called once' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) } - def 'Update cmHandle state to "DELETED"' (){ + def 'Update cmHandle state to DELETING to DELETED' (){ given: 'cm Handle with state "DELETING" as Yang model ' compositeState = new CompositeState(cmHandleState: DELETING) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETED"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETED)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETED and: 'the method to publish Lcm event is called once' @@ -157,14 +177,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('NO_CHANGE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then: 'batch is empty and nothing to update' - 1 * mockInventoryPersistence.saveCmHandleBatch(_) >> { - args -> { - assert (args[0] as Collection<YangModelCmHandle>).size() == 0 - } - } + then: 'no changes are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'no event will be published' 0 * mockLcmEventsService.publishLcmEvent(*_) + and: 'no log entries are written' + assert logger.list.empty } def 'Batch of new cm handles provided'() { @@ -178,8 +197,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { assert (args[0] as Collection<YangModelCmHandle>).id.containsAll('cmhandle1', 'cmhandle2') } } + and: 'no state updates are persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in ADVISED state' + assert getLogMessage(1) == 'cmhandle2 is now in ADVISED state' } def 'Batch of existing cm handles is updated'() { @@ -187,14 +211,19 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('UPDATE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' + then: 'existing cm handles composite states are persisted' 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1','cmhandle2']) + assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1', 'cmhandle2']) } } + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in READY state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETING state' } def 'Batch of existing cm handles is deleted'() { @@ -202,14 +231,30 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('DELETED') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' - 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { - args -> { - assert (args[0] as Map<String, CompositeState>).isEmpty() - } - } + then: 'state of deleted handles is not persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in DELETED state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETED state' + } + + def 'Log entries and events are not sent when an error occurs during persistence'() { + given: 'A batch of updated cm handles' + def cmHandleStateMap = setupBatch('UPDATE') + and: 'an error will be thrown when trying to persist' + mockInventoryPersistence.saveCmHandleStateBatch(_) >> { throw new RuntimeException() } + when: 'updating a batch of changes' + objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) + then: 'the exception is not handled' + thrown(RuntimeException) + and: 'no events are published' + 0 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'no log entries are written' + assert logger.list.empty } def setupBatch(type) { @@ -217,26 +262,31 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1', dmiProperties: [], publicProperties: []) def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2', dmiProperties: [], publicProperties: []) - if ('NEW' == type) { - return [yangModelCmHandle1, yangModelCmHandle2] - } + switch (type) { + case 'NEW': + return [yangModelCmHandle1, yangModelCmHandle2] - if ('DELETED' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - } + case 'DELETED': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - if ('UPDATE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] - } + case 'UPDATE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] + + case 'NO_CHANGE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] - if ('NO_CHANGE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] + default: + throw new IllegalArgumentException("batch type '${type}' not recognized") } } + + def getLogMessage(index) { + return logger.list[index].formattedMessage + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy index e79a471015..9391fa0f44 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy @@ -36,13 +36,17 @@ class TrustLevelCacheConfigSpec extends Specification { @Autowired private Map<String, TrustLevel> trustLevelPerCmHandle + def cleanupSpec() { + Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown() + } + def 'Hazelcast cache for trust level per dmi plugin'() { expect: 'system is able to create an instance of the trust level per dmi plugin cache' assert null != trustLevelPerDmiPlugin and: 'there is at least 1 instance' assert Hazelcast.allHazelcastInstances.size() > 0 and: 'Dmi Plugin Trust Level Cache is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceTrustLevelPerDmiPluginMap') + assert Hazelcast.allHazelcastInstances.name.contains('cps-and-ncmp-hazelcast-instance-test-config') } def 'Hazelcast cache for trust level per cm handle'() { @@ -51,13 +55,13 @@ class TrustLevelCacheConfigSpec extends Specification { and: 'there is at least 1 instance' assert Hazelcast.allHazelcastInstances.size() > 0 and: 'Hazelcast cache instance for trust level is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceTrustLevelPerCmHandleMap') + assert Hazelcast.allHazelcastInstances.name.contains('cps-and-ncmp-hazelcast-instance-test-config') } def 'Trust level cache configurations: #scenario'() { - when: 'retrieving the cache config for trustLevel' - def cacheConfig = Hazelcast.getHazelcastInstanceByName(hazelcastInstanceName).config - then: 'the cache config has the right cluster' + given: 'retrieving the common cache config' + def cacheConfig = Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').config + and: 'the cache config has the right cluster' assert cacheConfig.clusterName == 'cps-and-ncmp-test-caches' when: 'retrieving the map config for trustLevel' def mapConfig = cacheConfig.mapConfigs.get(hazelcastMapConfigName) @@ -65,14 +69,14 @@ class TrustLevelCacheConfigSpec extends Specification { assert mapConfig.backupCount == 1 assert mapConfig.asyncBackupCount == 0 where: 'the following caches are used' - scenario | hazelcastInstanceName | hazelcastMapConfigName - 'cmhandle map' | 'hazelcastInstanceTrustLevelPerCmHandleMap' | 'trustLevelPerCmHandleCacheConfig' - 'dmi plugin map' | 'hazelcastInstanceTrustLevelPerDmiPluginMap' | 'trustLevelPerDmiPluginCacheConfig' + scenario | hazelcastMapConfigName + 'cmhandle map' | 'trustLevelPerCmHandleCacheConfig' + 'dmi plugin map' | 'trustLevelPerDmiPluginCacheConfig' } def 'Verify deployment network configs for Distributed Caches'() { given: 'the Trust Level Per Dmi Plugin Cache config' - def trustLevelDmiPerPluginCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceTrustLevelPerDmiPluginMap').config.networkConfig + def trustLevelDmiPerPluginCacheConfig = Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').config.networkConfig expect: 'system created instance with correct config' assert trustLevelDmiPerPluginCacheConfig.join.autoDetectionConfig.enabled assert !trustLevelDmiPerPluginCacheConfig.join.kubernetesConfig.enabled @@ -80,7 +84,7 @@ class TrustLevelCacheConfigSpec extends Specification { def 'Verify deployment network configs for Cm Handle Distributed Caches'() { given: 'the Trust Level Per Cm Handle Cache config' - def trustLevelPerCmHandlePluginCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceTrustLevelPerCmHandleMap').config.networkConfig + def trustLevelPerCmHandlePluginCacheConfig = Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').config.networkConfig expect: 'system created instance with correct config' assert trustLevelPerCmHandlePluginCacheConfig.join.autoDetectionConfig.enabled assert !trustLevelPerCmHandlePluginCacheConfig.join.kubernetesConfig.enabled diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy index fe762f891a..e0f5f9c4f8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy @@ -149,6 +149,11 @@ class TrustLevelManagerSpec extends Specification { assert effectiveTrustLevel == TrustLevel.NONE } + def 'Select effective trust level when the trust level caches are empty (restart case)'() { + expect: 'effective trust level is NONE when cm-1 does not exist in the cache' + assert objectUnderTest.getEffectiveTrustLevel('ch-1') == TrustLevel.NONE + } + def 'CmHandle trust level removed'() { given: 'a cm handle' trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy index a497b4554a..bd1faa2705 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy @@ -42,11 +42,15 @@ class AlternateIdMatcherSpec extends Specification { expect: 'querying for alternate id a matching result found' assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null where: 'the following parameters are used' - scenario | targetAlternateId - 'exact match' | '/a/b' - 'parent match' | '/a/b/c' - 'grand parent match' | '/a/b/c/d' - 'trailing separator match' | '/a/b/' + scenario | targetAlternateId + 'exact match' | '/a/b' + 'parent match' | '/a/b/c' + 'grand parent match' | '/a/b/c/d' + 'trailing separator match' | '/a/b/' + 'trailing hash' | '/a/b#q' + 'trailing hash parent match' | '/a/b/c#q' + 'trailing hash grand parent match' | '/a/b/c/d#q' + 'trailing separator then hash match' | '/a/b/#q' } def 'Attempt to find longest alternate id match without any matches.'() { diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index df3375d5d0..12db639633 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -99,6 +99,7 @@ ncmp: # Custom Hazelcast Config. hazelcast: cluster-name: "cps-and-ncmp-test-caches" + instance-config-name: "cps-and-ncmp-hazelcast-instance-test-config" mode: kubernetes: enabled: false diff --git a/cps-ncmp-service/src/test/resources/dataOperationRequest.json b/cps-ncmp-service/src/test/resources/dataOperationRequest.json index f69b87631f..3faaf2b0dc 100644 --- a/cps-ncmp-service/src/test/resources/dataOperationRequest.json +++ b/cps-ncmp-service/src/test/resources/dataOperationRequest.json @@ -36,6 +36,17 @@ "ch4-dmi2", "ch6-dmi1" ] + }, + { + "operation": "read", + "operationId": "operational-16", + "datastore": "ncmp-datastore:passthrough-operational", + "options": "some option", + "resourceIdentifier": "some resource identifier", + "targetIds": [ + "ch4-dmi2", + "alt6-dmi1" + ] } ] } diff --git a/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json index 611d47d1a3..827250f5fd 100644 --- a/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json +++ b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json @@ -1 +1 @@ -[{"operationId":"operational-14","ids":["unknown-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"100","statusMessage":"cm handle id(s) not found"},{"operationId":"operational-14","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"},{"operationId":"running-12","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"}]
\ No newline at end of file +[{"operationId":"operational-14","ids":["unknown-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"100","statusMessage":"cm handle reference(s) not found"},{"operationId":"operational-14","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"},{"operationId":"running-12","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"}]
\ No newline at end of file diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index 430f4b5cd8..12103ed108 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -22,33 +22,96 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <parent> - <groupId>org.onap.oparent</groupId> - <artifactId>oparent</artifactId> - <version>3.2.0</version> - <relativePath/> - </parent> - <modelVersion>4.0.0</modelVersion> + <!-- Project Metadata --> + <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <properties> + <!-- Set UTF-8 encoding for consistent builds across platforms --> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <!-- Application Configuration --> <app>org.onap.cps.Application</app> + + <!-- Parent Directory Configuration --> + <parent.directory>${project.basedir}/..</parent.directory> + + <!-- Global properties for version management --> + <bug.pattern.version>1.5.0</bug.pattern.version> + <dependency.check.version>9.2.0</dependency.check.version> + <git.commit.id.version>9.0.1</git.commit.id.version> + <gmavenplus.plugin.version>4.0.1</gmavenplus.plugin.version> + <jacoco.version>0.8.11</jacoco.version> <java.version>17</java.version> - <minimum-coverage>1.00</minimum-coverage> + <jsonschema2pojo.maven.plugin.version>1.2.1</jsonschema2pojo.maven.plugin.version> + <maven.checkstyle.plugin.version>3.3.1</maven.checkstyle.plugin.version> + <maven.compiler.plugin.version>3.13.0</maven.compiler.plugin.version> + <maven.compiler.release>17</maven.compiler.release> + <maven.failsafe.plugin.version>3.5.2</maven.failsafe.plugin.version> + <maven.resources.plugin.version>3.3.1</maven.resources.plugin.version> + <maven.site.plugin.version>4.0.0-M13</maven.site.plugin.version> + <maven.surefire.plugin.version>3.3.1</maven.surefire.plugin.version> <postgres.version>42.5.1</postgres.version> + <slf4j.simple.version>2.0.6</slf4j.simple.version> + <sonar.version>4.0.0.4121</sonar.version> + <spotbugs.plugin.version>4.8.6.4</spotbugs.plugin.version> + <spotbugs.version>4.8.6</spotbugs.version> + <spring.boot.maven.plugin.version>3.3.1</spring.boot.maven.plugin.version> + <swagger.codegen.version>1.2.1</swagger.codegen.version> + <!-- Reporting paths and coverage --> + <jacoco.execFile>${project.build.directory}/code-coverage/jacoco-ut.exec</jacoco.execFile> + <jacoco.outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</jacoco.outputDirectory> <jacoco.reportDirectory.aggregate>${project.reporting.outputDirectory}/jacoco-aggregate</jacoco.reportDirectory.aggregate> + <minimum-coverage>1.00</minimum-coverage> <sonar.coverage.jacoco.xmlReportPaths> ../jacoco-report/target/site/jacoco-aggregate/jacoco.xml </sonar.coverage.jacoco.xmlReportPaths> - <parent.directory>${project.basedir}/..</parent.directory> - <maven.compiler.release>17</maven.compiler.release> + + <onap.nexus.url>https://nexus.onap.org</onap.nexus.url> + <nexusproxy>https://nexus.onap.org</nexusproxy> + <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> + <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> </properties> + <distributionManagement> + <repository> + <id>ecomp-releases</id> + <name>ECOMP Release Repository</name> + <url>${onap.nexus.url}${releaseNexusPath}</url> + </repository> + <snapshotRepository> + <id>ecomp-snapshots</id> + <name>ECOMP Snapshot Repository</name> + <url>${onap.nexus.url}${snapshotNexusPath}</url> + </snapshotRepository> + </distributionManagement> + + <!-- Dependency Management, Profiles, Build, and Plugins --> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.onap.cps</groupId> + <artifactId>cps-dependencies</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.onap.cps</groupId> + <artifactId>cps-bom</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + <profiles> <profile> <id>Windows</id> @@ -72,27 +135,57 @@ <script.executor>python3</script.executor> </properties> </profile> + <profile> + <id>dependency-vulnerability-check</id> + <build> + <plugins> + <plugin> + <groupId>org.owasp</groupId> + <artifactId>dependency-check-maven</artifactId> + <version>${dependency.check.version}</version> + <executions> + <execution> + <goals> + <goal>aggregate</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <!-- Performance tests are run with maven-failsafe-plugin using a separate profile, so they will + not affect Jacoco coverage. Heap size is set here to ensure consistent test environment. --> + <profile> + <id>include-performance</id> + <properties> + <failsafeArgLine>-Xms512m -Xmx512m</failsafeArgLine> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <version>${maven.failsafe.plugin.version}</version> + <configuration> + <includes> + <include>**/*PerfTest.java</include> + </includes> + </configuration> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> </profiles> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.onap.cps</groupId> - <artifactId>cps-dependencies</artifactId> - <version>${project.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - <dependency> - <groupId>org.onap.cps</groupId> - <artifactId>cps-bom</artifactId> - <version>${project.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - <build> <resources> <resource> @@ -118,20 +211,28 @@ <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> - <version>3.2.4</version> + <version>${spring.boot.maven.plugin.version}</version> <executions> <execution> <goals> - <goal>build-info</goal> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>3.0.0-M5</version> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + <version>${git.commit.id.version}</version> + <executions> + <execution> + <id>get-git-info</id> + <goals> + <goal>revision</goal> + </goals> + <phase>package</phase> + </execution> + </executions> </plugin> <!-- Swagger code generation. --> <plugin> @@ -154,12 +255,12 @@ <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> - <version>4.4.2</version> + <version>${spotbugs.plugin.version}</version> <dependencies> <dependency> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs</artifactId> - <version>4.2.3</version> + <version>${spotbugs.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> @@ -170,7 +271,7 @@ <!-- The SpotBugs Maven plugin uses SLF4J 1.8 beta 2 --> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> - <version>2.0.6</version> + <version>${slf4j.simple.version}</version> </dependency> </dependencies> <configuration> @@ -178,7 +279,7 @@ <plugin> <groupId>jp.skypencil.findbugs.slf4j</groupId> <artifactId>bug-pattern</artifactId> - <version>1.5.0</version> + <version>${bug.pattern.version}</version> </plugin> </plugins> <!-- @@ -215,7 +316,7 @@ <plugin> <groupId>org.jsonschema2pojo</groupId> <artifactId>jsonschema2pojo-maven-plugin</artifactId> - <version>1.2.1</version> + <version>${jsonschema2pojo.maven.plugin.version}</version> <configuration> <targetVersion>${java.version}</targetVersion> </configuration> @@ -233,11 +334,15 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.11.0</version> + <version>${maven.compiler.plugin.version}</version> + <configuration> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> + <version>${maven.checkstyle.plugin.version}</version> <executions> <execution> <id>onap-license</id> @@ -246,7 +351,7 @@ </goals> <phase>process-sources</phase> <configuration> - <configLocation>onap-checkstyle/check-license.xml</configLocation> + <configLocation>cps-checkstyle/check-license.xml</configLocation> <includeResources>false</includeResources> <includeTestSourceDirectory>true</includeTestSourceDirectory> <includeTestResources>false</includeTestResources> @@ -259,39 +364,20 @@ </configuration> </execution> <execution> - <id>onap-java-style</id> - <goals> - <goal>check</goal> - </goals> - <phase>process-sources</phase> - <configuration> - <configLocation>onap-checkstyle/onap-java-style.xml</configLocation> - <sourceDirectories> - <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory> - </sourceDirectories> - <includeResources>true</includeResources> - <includeTestSourceDirectory>true</includeTestSourceDirectory> - <includeTestResources>true</includeTestResources> - <consoleOutput>false</consoleOutput> - <violationSeverity>warning</violationSeverity> - <failOnViolation>true</failOnViolation> - </configuration> - </execution> - <execution> <id>cps-java-style</id> <goals> <goal>check</goal> </goals> <phase>process-sources</phase> <configuration> - <configLocation>cps-java-style.xml</configLocation> + <configLocation>cps-checkstyle/cps-java-style.xml</configLocation> <sourceDirectories> <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory> </sourceDirectories> <includeResources>true</includeResources> <includeTestSourceDirectory>true</includeTestSourceDirectory> <includeTestResources>true</includeTestResources> - <consoleOutput>true</consoleOutput> + <consoleOutput>false</consoleOutput> <violationSeverity>warning</violationSeverity> <failOnViolation>true</failOnViolation> </configuration> @@ -299,11 +385,6 @@ </executions> <dependencies> <dependency> - <groupId>org.onap.oparent</groupId> - <artifactId>checkstyle</artifactId> - <version>3.2.0</version> - </dependency> - <dependency> <groupId>${project.groupId}</groupId> <artifactId>checkstyle</artifactId> <version>${project.version}</version> @@ -316,7 +397,7 @@ To learn more about this plugin, visit https://github.com/groovy/GMavenPlus/wiki --> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> - <version>1.9.0</version> + <version>${gmavenplus.plugin.version}</version> <executions> <execution> <goals> @@ -330,6 +411,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> + <version>${maven.surefire.plugin.version}</version> <configuration> <!--suppress UnresolvedMavenProperty --> <argLine>${surefireArgLine}</argLine> @@ -358,17 +440,18 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.10</version> + <version>${jacoco.version}</version> <configuration> - <!--All exclusions below are referring to generated code--> + <!-- Exclude all generated classes or specific patterns if necessary --> <excludes> - <exclude>org/onap/cps/event/model/*</exclude> + <exclude>org/onap/cps/events/model/*</exclude> <exclude>org/onap/cps/rest/model/*</exclude> <exclude>org/onap/cps/cpspath/parser/antlr4/*</exclude> <exclude>org/onap/cps/ncmp/rest/model/*</exclude> <exclude>org/onap/cps/**/*MapperImpl.class</exclude> <exclude>org/onap/cps/ncmp/rest/stub/*</exclude> <exclude>org/onap/cps/policyexecutor/stub/model/*</exclude> + <exclude>**/pom.xml</exclude> </excludes> </configuration> <executions> @@ -377,14 +460,19 @@ <goals> <goal>prepare-agent</goal> </goals> + <configuration> + <destFile>${jacoco.execFile}</destFile> + <propertyName>surefireArgLine</propertyName> + </configuration> </execution> <execution> <id>coverage-check</id> + <phase>test</phase> <goals> <goal>check</goal> </goals> <configuration> - <dataFile>${project.build.directory}/code-coverage/jacoco-ut.exec</dataFile> + <dataFile>${jacoco.execFile}</dataFile> <rules> <rule> <element>BUNDLE</element> @@ -399,17 +487,48 @@ </rules> </configuration> </execution> + <execution> + <id>post-unit-test</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + <configuration> + <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> + <!-- Sets the path to the file which contains the execution data. --> + <dataFile>${jacoco.execFile}</dataFile> + <!-- Sets the output directory for the code coverage report. --> + <outputDirectory>${jacoco.outputDirectory}</outputDirectory> + </configuration> + </execution> </executions> </plugin> - <plugin> - <groupId>com.github.spotbugs</groupId> - <artifactId>spotbugs-maven-plugin</artifactId> - </plugin> + + <!-- Sonar Plugin for Code Quality --> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> - <version>3.9.1.2184</version> + <version>${sonar.version}</version> + </plugin> + + <!-- Maven Site Plugin for Site Generation --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <version>${maven.site.plugin.version}</version> + <executions> + <execution> + <id>default-site</id> + <phase>site</phase> + <goals><goal>site</goal></goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>3.1.2</version> </plugin> </plugins> </build> -</project> +</project>
\ No newline at end of file diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml index f71d9aabcd..b876c48810 100644 --- a/cps-path-parser/pom.xml +++ b/cps-path-parser/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -82,30 +82,4 @@ <scope>test</scope> </dependency> </dependencies> - - <profiles> - <profile> - <id>default</id> - <activation> - <activeByDefault>true</activeByDefault> - </activation> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <excludes> - <exclude>%regex[.*PerfTest.*]</exclude> - </excludes> - </configuration> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>include-performance</id> - </profile> - </profiles> - </project> diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 index 444702db48..74b99feb33 100644 --- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 +++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 @@ -91,7 +91,7 @@ KW_CONTAINS_FUNCTION : 'contains' ; KW_ANCESTOR_AXIS_PREFIX : SLASH KW_ANCESTOR COLONCOLON ; IntegerLiteral : FragDigits ; -// Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440 +// Add below type definitions for leafvalue comparision in https://lf-onap.atlassian.net/CPS-440 DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ; DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ; StringLiteral : '"' (~["] | FragEscapeQuot)* '"' | '\'' (~['] | FragEscapeApos)* '\'' ; diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java index bde9b0638f..4ede0d9c90 100644 --- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java +++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,12 +59,10 @@ public class CpsPathUtil { return getCpsPathBuilder(xpathSource).build().getNormalizedParentPath(); } - public static String[] getXpathNodeIdSequence(final String xpathSource) { - final List<String> containerNames = getCpsPathBuilder(xpathSource).build().getContainerNames(); - return containerNames.toArray(new String[containerNames.size()]); + public static List<String> getXpathNodeIdSequence(final String xpathSource) { + return getCpsPathBuilder(xpathSource).build().getContainerNames(); } - /** * Returns boolean indicating xpath is an absolute path to a list element. * diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml new file mode 100644 index 0000000000..728295fd31 --- /dev/null +++ b/cps-rest/docs/openapi/components.yml @@ -0,0 +1,416 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021-2022 Bell Canada. +# Modifications Copyright (C) 2021-2023 Nordix Foundation +# Modifications Copyright (C) 2022-2024 TechMahindra Ltd. +# Modifications Copyright (C) 2022 Deutsche Telekom AG +# ================================================================================ +# 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========================================================= + +components: + schemas: + + AnchorDetails: + type: object + title: Anchor details by anchor Name + properties: + name: + type: string + example: my-anchor + dataspaceName: + type: string + example: my-dataspace + schemaSetName: + type: string + example: my-schema-set + + DataspaceDetails: + type: object + title: Dataspace details by dataspace Name + properties: + name: + type: string + example: my-dataspace + + ErrorMessage: + type: object + title: Error + properties: + status: + type: string + message: + type: string + details: + type: string + + MultipartFile: + type: object + required: + - file + properties: + file: + type: string + description: multipartFile + format: binary + + ModuleReferences: + type: object + title: Module reference object + properties: + name: + type: string + example: my-module-reference-name + namespace: + type: string + example: my-module-reference-namespace + revision: + type: string + example: my-module-reference-revision + + SchemaSetDetails: + type: object + title: Schema set details by dataspace and schemasetName + required: + - "moduleReferences" + properties: + dataspaceName: + type: string + example: my-dataspace + moduleReferences: + type: array + items: + $ref: '#/components/schemas/ModuleReferences' + name: + type: string + example: my-schema-set + + examples: + dataSample: + value: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 01 + name: SciFi + - code: 02 + name: kids + dataSampleXml: + value: + <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <bookstore xmlns="org:onap:ccsdk:sample"> + <bookstore-name>Chapters</bookstore-name> + <categories> + <code>1</code> + <name>SciFi</name> + <code>2</code> + <name>kids</name> + </categories> + </bookstore> + </stores> + dataSampleAcrossAnchors: + value: + - anchorName: bookstore1 + dataNode: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 01 + name: SciFi + - code: 02 + name: kids + - anchorName: bookstore2 + dataNode: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 01 + name: SciFi + - code: 02 + name: kids + deltaReportSample: + value: + - action: "create" + xpath: "/bookstore/categories/[@code=3]" + target-data: + code: 3, + name: "kidz" + - action: "remove" + xpath: "/bookstore/categories/[@code=1]" + source-data: + code: 1, + name: "Fiction" + - action: "replace" + xpath: "/bookstore/categories/[@code=2]" + source-data: + name: "Funny" + target-data: + name: "Comic" + + parameters: + dataspaceNameInQuery: + name: dataspace-name + in: query + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + dataspaceNameInPath: + name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + anchorNameInPath: + name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + sourceAnchorNameInPath: + name: source-anchor-name + in: path + description: source-anchor-name + required: true + schema: + type: string + example: my-anchor + schemaSetNameInQuery: + name: schema-set-name + in: query + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set + schemaSetNameInPath: + name: schema-set-name + in: path + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set + anchorNameInQuery: + name: anchor-name + in: query + description: anchor-name + required: true + schema: + type: string + example: my-anchor + targetAnchorNameInQuery: + name: target-anchor-name + in: query + description: target-anchor-name + required: true + schema: + type: string + example: my-anchor + xpathInQuery: + name: xpath + in: query + description: For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: /shops/bookstore/categories[@code=1] + requiredXpathInQuery: + name: xpath + in: query + description: For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html + required: true + schema: + type: string + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: /shops/bookstore/categories[@code=1] + cpsPathInQuery: + name: cps-path + in: query + description: For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html + required: false + schema: + type: string + default: / + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: //categories[@code=1] + includeDescendantsOptionInQuery: + name: include-descendants + in: query + description: include-descendants + required: false + schema: + type: boolean + default: false + example: false + observedTimestampInQuery: + name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: '2021-03-21T00:10:34.030-0100' + apiVersionInPath: + name: apiVersion + in: path + description: apiVersion + required: true + schema: + type: string + enum: [v1, v2] + default: v2 + contentTypeInHeader: + name: Content-Type + in: header + description: Content type in header + schema: + type: string + enum: + - application/json + - application/xml + required: true + descendantsInQuery: + name: descendants + in: query + description: Number of descendants to query. Allowed values are 'none', 'all', 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive number. + required: false + schema: + type: string + default: none + example: 3 + pageIndexInQuery: + name: pageIndex + in: query + description: page index for pagination over anchors. It must be greater then zero if provided. + required: false + schema: + type: integer + example: 1 + pageSizeInQuery: + name: pageSize + in: query + description: number of records (anchors) per page. It must be greater then zero if provided. + required: false + schema: + type: integer + example: 10 + dryRunInQuery: + name: dry-run + in: query + description: Boolean flag to validate data, without persisting it. Default value is set to false. + required: false + schema: + type: boolean + default: false + example: false + + responses: + NotFound: + description: The specified resource was not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 404 + message: Resource Not Found + details: The requested resource is not found + Unauthorized: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + Forbidden: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + BadRequest: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid + Conflict: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + Ok: + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + value: "" + Created: + description: Created + content: + application/json: + schema: + type: string + example: my-resource + CreatedV2: + description: Created without response body + InternalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorMessage" + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + NoContent: + description: No Content + content: {} diff --git a/cps-rest/docs/openapi/cpsAdmin.yml b/cps-rest/docs/openapi/cpsAdmin.yml new file mode 100644 index 0000000000..f394270dd5 --- /dev/null +++ b/cps-rest/docs/openapi/cpsAdmin.yml @@ -0,0 +1,232 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021 Bell Canada. +# Modifications Copyright (C) 2021-2022 Nordix Foundation +# Modifications Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +dataspaces: + delete: + description: Delete a dataspace + tags: + - cps-admin + summary: Delete a dataspace + operationId: deleteDataspace + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSet: + get: + description: Read all schema sets, given a dataspace + tags: + - cps-admin + summary: Get schema sets + operationId: getSchemaSets + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: 'components.yml#/components/schemas/SchemaSetDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSetBySchemaSetName: + get: + description: Read a schema set given a schema set name and a dataspace + tags: + - cps-admin + summary: Get a schema set + operationId: getSchemaSet + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/SchemaSetDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + delete: + description: Delete a schema set given a schema set name and a dataspace + tags: + - cps-admin + summary: Delete a schema set + operationId: deleteSchemaSet + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInPath' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorsByDataspace: + get: + description: Read all anchors, given a dataspace + tags: + - cps-admin + summary: Get anchors + operationId: getAnchors + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: 'components.yml#/components/schemas/AnchorDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorByDataspaceAndAnchorName: + get: + description: Read an anchor given an anchor name and a dataspace + tags: + - cps-admin + summary: Get an anchor + operationId: getAnchor + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/AnchorDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + delete: + description: Delete an anchor given an anchor name and a dataspace + tags: + - cps-admin + summary: Delete an anchor + operationId: deleteAnchor + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +adminDataspaces: + get: + description: Read all dataspaces + tags: + - cps-admin + summary: Get all dataspaces + operationId: getAllDataspaces + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: 'components.yml#/components/schemas/DataspaceDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +adminDataspace: + get: + description: Read a dataspace given a dataspace name + tags: + - cps-admin + summary: Get a dataspace + operationId: getDataspace + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/DataspaceDetails' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml b/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml new file mode 100644 index 0000000000..c92f773c30 --- /dev/null +++ b/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml @@ -0,0 +1,92 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +dataspaces: + post: + deprecated: true + description: Create a new dataspace + tags: + - cps-admin + summary: Create a dataspace + operationId: createDataspace + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorsByDataspace: + post: + deprecated: true + description: Create a new anchor in the given dataspace + tags: + - cps-admin + summary: Create an anchor + operationId: createAnchor + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + - $ref: 'components.yml#/components/parameters/anchorNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSet: + post: + deprecated: true + description: Create a new schema set in the given dataspace + tags: + - cps-admin + summary: Create a schema set + operationId: createSchemaSet + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: 'components.yml#/components/schemas/MultipartFile' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsAdminV2.yml b/cps-rest/docs/openapi/cpsAdminV2.yml new file mode 100644 index 0000000000..e501ad8b15 --- /dev/null +++ b/cps-rest/docs/openapi/cpsAdminV2.yml @@ -0,0 +1,89 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +dataspaces: + post: + description: Create a new dataspace + tags: + - cps-admin + summary: Create a dataspace + operationId: createDataspaceV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorsByDataspace: + post: + description: Create a new anchor in the given dataspace + tags: + - cps-admin + summary: Create an anchor + operationId: createAnchorV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + - $ref: 'components.yml#/components/parameters/anchorNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSet: + post: + description: Create a new schema set in the given dataspace + tags: + - cps-admin + summary: Create a schema set + operationId: createSchemaSetV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: 'components.yml#/components/schemas/MultipartFile' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml new file mode 100644 index 0000000000..daf59bbfbf --- /dev/null +++ b/cps-rest/docs/openapi/cpsData.yml @@ -0,0 +1,235 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021-2022 Bell Canada. +# Modifications Copyright (C) 2021-2022 Nordix Foundation +# Modifications Copyright (C) 2022-2024 TechMahindra Ltd. +# Modifications Copyright (C) 2022 Deutsche Telekom AG +# ================================================================================ +# 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========================================================= + +listElementByDataspaceAndAnchor: + post: + description: Add list element(s) to a list for a given anchor and dataspace + tags: + - cps-data + summary: Add list element(s) + operationId: addListElements + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + requestBody: + required: true + content: + application/json: + schema: + type: string + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + put: + description: Replace list content under a given parent, anchor and dataspace + tags: + - cps-data + summary: Replace list content + operationId: replaceListContent + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + requestBody: + required: true + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + responses: + '200': + $ref: 'components.yml#/components/responses/Ok' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +nodesByDataspaceAndAnchor: + post: + description: Create a node for a given anchor and dataspace + tags: + - cps-data + summary: Create a node + operationId: createNode + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/dryRunInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + requestBody: + required: true + content: + application/json: + schema: + type: string + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object # Workaround to show example + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + patch: + description: Update a data node leaves for a given dataspace and anchor and a parent node xpath. This operation + is currently supported for one top level data node only. + tags: + - cps-data + summary: Update node leaves + operationId: updateNodeLeaves + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + requestBody: + required: true + content: + application/json: + schema: + type: string + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + responses: + '200': + $ref: 'components.yml#/components/responses/Ok' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + delete: + description: Delete a datanode for a given dataspace and anchor given a node xpath. + tags: + - cps-data + summary: Delete a data node + operationId: deleteDataNode + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + put: + description: Replace a node with descendants for a given dataspace, anchor and a parent node xpath + tags: + - cps-data + summary: Replace a node with descendants + operationId: replaceNode + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + requestBody: + required: true + content: + application/json: + schema: + type: string + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + responses: + '200': + $ref: 'components.yml#/components/responses/Ok' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsDataV1Deprecated.yml b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml new file mode 100644 index 0000000000..3941856422 --- /dev/null +++ b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml @@ -0,0 +1,71 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022-2023 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +nodeByDataspaceAndAnchor: + get: + description: Get a node with an option to retrieve all the children for a given anchor and dataspace + deprecated: true + tags: + - cps-data + summary: Get a node + operationId: getNodeByDataspaceAndAnchor + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath + +listElementByDataspaceAndAnchor: + delete: + description: Delete one or all list element(s) for a given anchor and dataspace + deprecated: true + tags: + - cps-data + summary: Delete one or all list element(s) + operationId: deleteListOrListElement + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml new file mode 100644 index 0000000000..999c5b2c19 --- /dev/null +++ b/cps-rest/docs/openapi/cpsDataV2.yml @@ -0,0 +1,136 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2022-2024 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +nodeByDataspaceAndAnchor: + get: + description: Get a node with an option to retrieve all the children for a given anchor and dataspace + tags: + - cps-data + summary: Get a node + operationId: getNodeByDataspaceAndAnchorV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath + +delta: + get: + description: Get delta between two anchors within a given dataspace + tags: + - cps-data + summary: Get delta between anchors in the same dataspace + operationId: getDeltaByDataspaceAndAnchors + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/sourceAnchorNameInPath' + - $ref: 'components.yml#/components/parameters/targetAnchorNameInQuery' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/deltaReportSample' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath + post: + description: Get delta between an anchor in a dataspace and JSON payload + tags: + - cps-data + summary: Get delta between an anchor and JSON payload + operationId: getDeltaByDataspaceAnchorAndPayload + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/sourceAnchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + json: + type: object + example: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 01 + name: SciFi + - code: 02 + name: kids + file: + type: string + format: binary + required: + - json + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/deltaReportSample' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError'
\ No newline at end of file diff --git a/cps-rest/docs/openapi/cpsQueryV1Deprecated.yml b/cps-rest/docs/openapi/cpsQueryV1Deprecated.yml new file mode 100644 index 0000000000..9db2823994 --- /dev/null +++ b/cps-rest/docs/openapi/cpsQueryV1Deprecated.yml @@ -0,0 +1,50 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2021 Nordix Foundation +# Modifications Copyright (c) 2022 Bell Canada. +# Modifications Copyright (c) 2023 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +nodesByDataspaceAndAnchorAndCpsPath: + get: + description: Query data nodes for the given dataspace and anchor using CPS path + tags: + - cps-query + summary: Query data nodes + deprecated: true + operationId: getNodesByDataspaceAndAnchorAndCpsPath + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/cpsPathInQuery' + - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath diff --git a/cps-rest/docs/openapi/cpsQueryV2.yml b/cps-rest/docs/openapi/cpsQueryV2.yml new file mode 100644 index 0000000000..9aaa4193c3 --- /dev/null +++ b/cps-rest/docs/openapi/cpsQueryV2.yml @@ -0,0 +1,88 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2023 TechMahindra Ltd. +# Modifications Copyright (C) 2023-2024 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +nodesByDataspaceAndAnchorAndCpsPath: + get: + description: Query data nodes for the given dataspace and anchor using CPS path + tags: + - cps-query + summary: Query data nodes + operationId: getNodesByDataspaceAndAnchorAndCpsPathV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/cpsPathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath + +nodesByDataspaceAndCpsPath: + get: + description: Query data nodes for the given dataspace across anchors using CPS path + tags: + - cps-query + summary: Query data nodes across anchors + operationId: getNodesByDataspaceAndCpsPath + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/cpsPathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + - $ref: 'components.yml#/components/parameters/pageIndexInQuery' + - $ref: 'components.yml#/components/parameters/pageSizeInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleAcrossAnchors' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + x-codegen-request-body-name: xpath diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml new file mode 100644 index 0000000000..95c32312cf --- /dev/null +++ b/cps-rest/docs/openapi/openapi.yml @@ -0,0 +1,116 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2021-2024 Nordix Foundation +# Modifications Copyright (C) 2021 Pantheon.tech +# Modifications Copyright (C) 2021 Bell Canada. +# Modifications Copyright (C) 2022-2024 TechMahindra Ltd. +# ================================================================================ +# 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========================================================= + +openapi: 3.0.3 +info: + title: ONAP Open API v3 Configuration Persistence Service + description: Configuration Persistence Service is a Model Driven Generic Database + version: "3.5.4" + contact: + name: ONAP + url: "https://onap.readthedocs.io" + email: "onap-discuss@lists.onap.org" + license: + name: "Apache 2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0" + +servers: + - url: /cps/api +components: + securitySchemes: + basicAuth: + type: http + scheme: basic +tags: + - name: cps-admin + description: cps Admin + - name: cps-data + description: cps Data +paths: + + /v1/dataspaces: + $ref: 'cpsAdminV1Deprecated.yml#/dataspaces' + + /{apiVersion}/dataspaces: + $ref: 'cpsAdmin.yml#/dataspaces' + + /v2/dataspaces: + $ref: 'cpsAdminV2.yml#/dataspaces' + + /{apiVersion}/admin/dataspaces: + $ref: 'cpsAdmin.yml#/adminDataspaces' + + /{apiVersion}/admin/dataspaces/{dataspace-name}: + $ref: 'cpsAdmin.yml#/adminDataspace' + + /v1/dataspaces/{dataspace-name}/anchors: + $ref: 'cpsAdminV1Deprecated.yml#/anchorsByDataspace' + + /v2/dataspaces/{dataspace-name}/anchors: + $ref: 'cpsAdminV2.yml#/anchorsByDataspace' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors: + $ref: 'cpsAdmin.yml#/anchorsByDataspace' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}: + $ref: 'cpsAdmin.yml#/anchorByDataspaceAndAnchorName' + + /v1/dataspaces/{dataspace-name}/schema-sets: + $ref: 'cpsAdminV1Deprecated.yml#/schemaSet' + + /v2/dataspaces/{dataspace-name}/schema-sets: + $ref: 'cpsAdminV2.yml#/schemaSet' + + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets: + $ref: 'cpsAdmin.yml#/schemaSet' + + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: + $ref: 'cpsAdmin.yml#/schemaSetBySchemaSetName' + + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + $ref: 'cpsDataV1Deprecated.yml#/nodeByDataspaceAndAnchor' + + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + $ref: 'cpsDataV2.yml#/nodeByDataspaceAndAnchor' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: + $ref: 'cpsData.yml#/nodesByDataspaceAndAnchor' + + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: + $ref: 'cpsDataV1Deprecated.yml#/listElementByDataspaceAndAnchor' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: + $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor' + + /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta: + $ref: 'cpsDataV2.yml#/delta' + + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + $ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath' + + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + $ref: 'cpsQueryV2.yml#/nodesByDataspaceAndAnchorAndCpsPath' + + /v2/dataspaces/{dataspace-name}/nodes/query: + $ref: 'cpsQueryV2.yml#/nodesByDataspaceAndCpsPath' + +security: + - basicAuth: [] diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index f115fdfd52..f270a58f06 100644 --- a/cps-rest/pom.xml +++ b/cps-rest/pom.xml @@ -27,10 +27,14 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> + <properties> + <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> + </properties> + <artifactId>cps-rest</artifactId> <dependencies> @@ -141,7 +145,7 @@ <goal>generate</goal> </goals> <configuration> - <inputSpec>${project.basedir}/../docs/api/swagger/cps/openapi.yaml</inputSpec> + <inputSpec>${project.basedir}/docs/openapi/openapi.yml</inputSpec> <invokerPackage>org.onap.cps.rest.controller</invokerPackage> <modelPackage>org.onap.cps.rest.model</modelPackage> <apiPackage>org.onap.cps.rest.api</apiPackage> @@ -165,7 +169,7 @@ </goals> <phase>compile</phase> <configuration> - <inputSpec>${project.basedir}/../docs/api/swagger/cps/openapi.yaml</inputSpec> + <inputSpec>${project.basedir}/docs/openapi/openapi.yml</inputSpec> <generatorName>openapi-yaml</generatorName> <configOptions> <outputFile>openapi.yaml</outputFile> @@ -174,6 +178,50 @@ </execution> </executions> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>${maven-resources-plugin.version}</version> + <executions> + <execution> + <id>copy-resources</id> + <phase>compile</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-core</outputDirectory> + <resources> + <resource> + <directory>${project.basedir}/target/generated-sources/openapi/</directory> + <includes> + <include>openapi.yaml</include> + </includes> + </resource> + </resources> + </configuration> + </execution> + <execution> + <id>copy-to-doc-folder</id> + <phase>compile</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/../docs/api/swagger/cps</outputDirectory> + <overwrite>true</overwrite> + <resources> + <resource> + <directory>${project.basedir}/target/generated-sources/openapi/</directory> + <includes> + <include>openapi.yaml</include> + </includes> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> </plugins> </build> </project> diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index f86073fb06..6d22581845 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -48,8 +48,8 @@ import org.onap.cps.utils.ContentType; import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; import org.onap.cps.utils.PrefixResolver; +import org.onap.cps.utils.XmlFileUtils; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -74,16 +74,21 @@ public class DataRestController implements CpsDataApi { final String dataspaceName, final String anchorName, final String contentTypeInHeader, final String nodeData, final String parentNodeXpath, - final String observedTimestamp) { - final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader); - if (isRootXpath(parentNodeXpath)) { - cpsDataService.saveData(dataspaceName, anchorName, nodeData, - toOffsetDateTime(observedTimestamp), contentType); + final Boolean dryRunEnabled, final String observedTimestamp) { + final ContentType contentType = ContentType.fromString(contentTypeInHeader); + if (Boolean.TRUE.equals(dryRunEnabled)) { + cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType); + return ResponseEntity.ok().build(); } else { - cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, - nodeData, toOffsetDateTime(observedTimestamp), contentType); + if (isRootXpath(parentNodeXpath)) { + cpsDataService.saveData(dataspaceName, anchorName, nodeData, + toOffsetDateTime(observedTimestamp), contentType); + } else { + cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, + nodeData, toOffsetDateTime(observedTimestamp), contentType); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); } - return new ResponseEntity<>(HttpStatus.CREATED); } @Override @@ -100,7 +105,7 @@ public class DataRestController implements CpsDataApi { final String anchorName, final String parentNodeXpath, final String contentTypeInHeader, final String nodeData, final String observedTimestamp) { - final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader); + final ContentType contentType = ContentType.fromString(contentTypeInHeader); cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, nodeData, toOffsetDateTime(observedTimestamp), contentType); return new ResponseEntity<>(HttpStatus.CREATED); @@ -124,8 +129,9 @@ public class DataRestController implements CpsDataApi { @Timed(value = "cps.data.controller.datanode.get.v2", description = "Time taken to get data node") public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName, - final String xpath, + final String contentTypeInHeader, final String xpath, final String fetchDescendantsOptionAsString) { + final ContentType contentType = ContentType.fromString(contentTypeInHeader); final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, @@ -137,7 +143,7 @@ public class DataRestController implements CpsDataApi { final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); dataMaps.add(dataMap); } - return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK); + return buildResponseEntity(dataMaps, contentType); } @Override @@ -145,7 +151,7 @@ public class DataRestController implements CpsDataApi { final String anchorName, final String contentTypeInHeader, final String nodeData, final String parentNodeXpath, final String observedTimestamp) { - final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader); + final ContentType contentType = ContentType.fromString(contentTypeInHeader); cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, nodeData, toOffsetDateTime(observedTimestamp), contentType); return new ResponseEntity<>(HttpStatus.OK); @@ -156,7 +162,7 @@ public class DataRestController implements CpsDataApi { final String anchorName, final String contentTypeInHeader, final String nodeData, final String parentNodeXpath, final String observedTimestamp) { - final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader); + final ContentType contentType = ContentType.fromString(contentTypeInHeader); cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, nodeData, toOffsetDateTime(observedTimestamp), contentType); return new ResponseEntity<>(HttpStatus.OK); @@ -214,12 +220,19 @@ public class DataRestController implements CpsDataApi { final List<DeltaReport> deltaBetweenAnchors = cpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchorName, - targetAnchorName, xpath, fetchDescendantsOption); + targetAnchorName, xpath, fetchDescendantsOption); return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaBetweenAnchors), HttpStatus.OK); } - private static ContentType getContentTypeFromHeader(final String contentTypeInHeader) { - return contentTypeInHeader.contains(MediaType.APPLICATION_XML_VALUE) ? ContentType.XML : ContentType.JSON; + ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataMaps, + final ContentType contentType) { + final String responseData; + if (contentType == ContentType.XML) { + responseData = XmlFileUtils.convertDataMapsToXml(dataMaps); + } else { + responseData = jsonObjectMapper.asJsonString(dataMaps); + } + return new ResponseEntity<>(responseData, HttpStatus.OK); } private static boolean isRootXpath(final String xpath) { diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java index 547be669ae..6823f6b03e 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. - * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,9 +36,11 @@ import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.PaginationOption; import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.DataNode; +import org.onap.cps.utils.ContentType; import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; import org.onap.cps.utils.PrefixResolver; +import org.onap.cps.utils.XmlFileUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -62,18 +64,20 @@ public class QueryRestController implements CpsQueryApi { final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, - fetchDescendantsOption); + fetchDescendantsOption, ContentType.JSON); } @Override @Timed(value = "cps.data.controller.datanode.query.v2", description = "Time taken to query data nodes") public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPathV2(final String dataspaceName, - final String anchorName, final String cpsPath, final String fetchDescendantsOptionAsString) { + final String anchorName, final String contentTypeInHeader, final String cpsPath, + final String fetchDescendantsOptionAsString) { + final ContentType contentType = ContentType.fromString(contentTypeInHeader); final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, - fetchDescendantsOption); + fetchDescendantsOption, contentType); } @Override @@ -130,7 +134,8 @@ public class QueryRestController implements CpsQueryApi { } private ResponseEntity<Object> executeNodesByDataspaceQueryAndCreateResponse(final String dataspaceName, - final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { + final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption, + final ContentType contentType) { final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption); final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size()); @@ -143,6 +148,17 @@ public class QueryRestController implements CpsQueryApi { final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); dataNodesAsListOfMaps.add(dataMap); } - return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataNodesAsListOfMaps), HttpStatus.OK); + return buildResponseEntity(dataNodesAsListOfMaps, contentType); + } + + private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsListOfMaps, + final ContentType contentType) { + final String responseData; + if (contentType == ContentType.XML) { + responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsListOfMaps); + } else { + responseData = jsonObjectMapper.asJsonString(dataNodesAsListOfMaps); + } + return new ResponseEntity<>(responseData, HttpStatus.OK); } } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index e101ea6c41..27738b07c6 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -164,6 +164,26 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON } + def 'Validate data using create a node API'() { + given: 'an endpoint to create a node' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" + def parentNodeXpath = '/' + def dryRunEnabled = 'true' + when: 'post is invoked with json data and dry-run flag enabled' + def response = + mvc.perform( + post(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', parentNodeXpath) + .param('dry-run', dryRunEnabled) + .content(requestBodyJson) + ).andReturn().response + then: 'a 200 OK response is returned' + response.status == HttpStatus.OK.value() + then: 'the service was called with correct parameters' + 1 * mockCpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, requestBodyJson, ContentType.JSON) + } + def 'Create a child node #scenario'() { given: 'endpoint to create a node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" @@ -294,7 +314,9 @@ class DataRestControllerSpec extends Specification { mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2] when: 'V2 of get request is performed through REST API' def response = - mvc.perform(get(endpoint).param('xpath', xpath)) + mvc.perform(get(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', xpath)) .andReturn().response then: 'a success response is returned' response.status == HttpStatus.OK.value() @@ -306,6 +328,21 @@ class DataRestControllerSpec extends Specification { assert numberOfDataTrees == 2 } + def 'Get all the data trees as XML with root node xPath using V2'() { + given: 'the service returns all data node leaves' + def xpath = '/' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren] + when: 'V2 of get request is performed through REST API with XML content type' + def response = + mvc.perform(get(endpoint).contentType(MediaType.APPLICATION_XML).param('xpath', xpath)) + .andReturn().response + then: 'a success response is returned' + response.status == HttpStatus.OK.value() + and: 'the response contains the datanode in XML format' + response.getContentAsString() == '<parent-1><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></parent-1>' + } + def 'Get data node with #scenario using V2.'() { given: 'the service returns data nodes with #scenario' def xpath = 'some xPath' @@ -315,6 +352,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform( get(endpoint) + .contentType(MediaType.APPLICATION_JSON) .param('xpath', xpath) .param('descendants', includeDescendantsOption)) .andReturn().response @@ -341,6 +379,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform( get(endpoint) + .contentType(MediaType.APPLICATION_JSON) .param('xpath', xpath) .param('descendants', '2')) .andReturn().response diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy index 80b287cda8..076ab32454 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy @@ -3,7 +3,7 @@ * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import spock.lang.Specification @@ -97,26 +98,52 @@ class QueryRestControllerSpec extends Specification { 'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS } - def 'Query data node v2 api by cps path for the given dataspace and anchor with #scenario.'() { + def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type JSON'() { given: 'service method returns a list containing a data node' - def dataNode1 = new DataNodeBuilder().withXpath('/xpath') + def dataNode = new DataNodeBuilder().withXpath('/xpath') .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> { - assert descendantsOption.depth == expectedDepth}}) >> [dataNode1, dataNode1] + mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> + assert descendantsOption.depth == expectedDepth + }) >> [dataNode, dataNode] when: 'query data nodes API is invoked' def response = mvc.perform( - get(dataNodeEndpointV2) - .param('cps-path', cpsPath) - .param('descendants', includeDescendantsOptionString)) - .andReturn().response - then: 'the response contains the the datanode in json format' + get(dataNodeEndpointV2) + .contentType(MediaType.APPLICATION_JSON) + .param('cps-path', cpsPath) + .param('descendants', includeDescendantsOptionString)) + .andReturn().response + then: 'the response contains the datanode in the expected JSON format' assert response.status == HttpStatus.OK.value() assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') - where: 'the following options for include descendants are provided in the request' - scenario | includeDescendantsOptionString || expectedDepth - 'direct children' | 'direct' || 1 - 'descendants' | '2' || 2 + where: 'the following options for include descendants are provided in the request' + scenario | includeDescendantsOptionString || expectedDepth + 'direct children' | 'direct' || 1 + 'descendants' | '2' || 2 + } + + def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type XML'() { + given: 'service method returns a list containing a data node' + def dataNode = new DataNodeBuilder().withXpath('/xpath') + .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() + mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> + assert descendantsOption.depth == expectedDepth + }) >> [dataNode, dataNode] + when: 'query data nodes API is invoked' + def response = + mvc.perform( + get(dataNodeEndpointV2) + .contentType(MediaType.APPLICATION_XML) + .param('cps-path', cpsPath) + .param('descendants', includeDescendantsOptionString)) + .andReturn().response + then: 'the response contains the datanode in the expected XML format' + assert response.status == HttpStatus.OK.value() + assert response.getContentAsString().contains('<xpath><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></xpath>') + where: 'the following options for include descendants are provided in the request' + scenario | includeDescendantsOptionString || expectedDepth + 'direct children' | 'direct' || 1 + 'descendants' | '2' || 2 } def 'Query data node by cps path for the given dataspace across all anchors with #scenario.'() { diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml index 57e6528441..1ab5920ef4 100644 --- a/cps-ri/pom.xml +++ b/cps-ri/pom.xml @@ -26,7 +26,7 @@ <parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.5.3-SNAPSHOT</version>
+ <version>3.5.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
@@ -68,6 +68,11 @@ <artifactId>postgresql</artifactId>
<version>${postgres.version}</version>
</dependency>
+ <!-- Disable SpotBug Rules -->
+ <dependency>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-annotations</artifactId>
+ </dependency>
<!-- Add Hibernate support for Postgres datatype JSONB and Postgres arrays -->
<dependency>
<groupId>io.hypersistence</groupId>
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java index ec46fea4cb..ecbe447226 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java @@ -228,6 +228,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final Collection<String> xpaths = xpathToUpdatedDataNode.keySet(); Collection<FragmentEntity> existingFragmentEntities = getFragmentEntities(anchorEntity, xpaths); + + logMissingXPaths(xpaths, existingFragmentEntities); + existingFragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities( FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS, existingFragmentEntities); @@ -243,6 +246,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } } + private void logMissingXPaths(final Collection<String> xpaths, final Collection<FragmentEntity> + existingFragmentEntities) { + final Set<String> existingXPaths = existingFragmentEntities.stream().map(FragmentEntity::getXpath) + .collect(Collectors.toSet()); + + final Set<String> missingXPaths = xpaths.stream().filter(xpath -> !existingXPaths.contains(xpath)) + .collect(Collectors.toSet()); + + if (!missingXPaths.isEmpty()) { + log.warn("Cannot update data nodes: Target XPaths {} not found in DB.", missingXPaths); + } + } + private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity, final Collection<FragmentEntity> fragmentEntities) { final Collection<String> failedXpaths = new HashSet<>(); @@ -341,8 +357,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService if (anchorIds.isEmpty()) { fragmentEntities = fragmentRepository.findByDataspaceAndXpathIn(dataspaceEntity, ancestorXpaths); } else { - fragmentEntities = fragmentRepository.findByAnchorIdsAndXpathIn( - anchorIds.toArray(new Long[0]), ancestorXpaths.toArray(new String[0])); + fragmentEntities = fragmentRepository.findByAnchorIdsAndXpathIn(anchorIds, ancestorXpaths); } } @@ -475,7 +490,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist); if (onlySupportListDeletion) { final Collection<String> xpathsToExistingListElements = xpathsToExistingContainers.stream() - .filter(CpsPathUtil::isPathToListElement).collect(Collectors.toList()); + .filter(CpsPathUtil::isPathToListElement).toList(); deleteChecklist.removeAll(xpathsToExistingListElements); } else { deleteChecklist.removeAll(xpathsToExistingContainers); @@ -483,15 +498,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final Collection<String> xpathsToExistingLists = deleteChecklist.stream() .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "[")) - .collect(Collectors.toList()); + .toList(); deleteChecklist.removeAll(xpathsToExistingLists); if (!deleteChecklist.isEmpty()) { throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, deleteChecklist); } - fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers); - fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingLists); + if (!xpathsToExistingContainers.isEmpty()) { + fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers); + } + for (final String listXpath : xpathsToExistingLists) { + fragmentRepository.deleteListByAnchorIdAndXpath(anchorEntity.getId(), listXpath); + } } @Override diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/AnchorRepository.java index 7fe14b3173..f7f750c983 100755 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/AnchorRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/AnchorRepository.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * Modifications Copyright (C) 2021-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,26 +46,26 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Long> { Collection<AnchorEntity> findAllBySchemaSet(SchemaSetEntity schemaSetEntity); - @Query(value = "SELECT * FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)", + @Query(value = "SELECT * FROM anchor WHERE dataspace_id = :dataspaceId AND name IN (:anchorNames)", nativeQuery = true) Collection<AnchorEntity> findAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId, - @Param("anchorNames") String[] anchorNames); + @Param("anchorNames") Collection<String> anchorNames); default Collection<AnchorEntity> findAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, final Collection<String> anchorNames) { - return findAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0])); + return findAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames); } @Query(value = "SELECT a.* FROM anchor a" + " LEFT OUTER JOIN schema_set s ON a.schema_set_id = s.id" - + " WHERE a.dataspace_id = :dataspaceId AND s.name = ANY (:schemaSetNames)", + + " WHERE a.dataspace_id = :dataspaceId AND s.name IN (:schemaSetNames)", nativeQuery = true) - Collection<AnchorEntity> findAllByDataspaceIdAndSchemaSetNameIn(@Param("dataspaceId") int dataspaceId, - @Param("schemaSetNames") String[] schemaSetNames); + Collection<AnchorEntity> findAllByDataspaceIdAndSchemaSetNameIn( + @Param("dataspaceId") int dataspaceId, @Param("schemaSetNames") Collection<String> schemaSetNames); default Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(final DataspaceEntity dataspaceEntity, final Collection<String> schemaSetNames) { - return findAllByDataspaceIdAndSchemaSetNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0])); + return findAllByDataspaceIdAndSchemaSetNameIn(dataspaceEntity.getId(), schemaSetNames); } Integer countByDataspace(DataspaceEntity dataspaceEntity); @@ -80,7 +80,7 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Long> { JOIN anchor ON anchor.schema_set_id = schema_set.id WHERE schema_set.dataspace_id = :dataspaceId - AND module_name = ANY ( :moduleNames ) + AND module_name IN (:moduleNames) GROUP BY anchor.id, anchor.name, @@ -90,25 +90,18 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Long> { COUNT(DISTINCT module_name) = :sizeOfModuleNames """, nativeQuery = true) Collection<String> getAnchorNamesByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId, - @Param("moduleNames") String[] moduleNames, + @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames); - default Collection<String> getAnchorNamesByDataspaceIdAndModuleNames(final int dataspaceId, - final Collection<String> moduleNames, - final int sizeOfModuleNames) { - final String[] moduleNamesArray = moduleNames.toArray(new String[0]); - return getAnchorNamesByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames); - } - @Modifying - @Query(value = "DELETE FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)", + @Query(value = "DELETE FROM anchor WHERE dataspace_id = :dataspaceId AND name IN (:anchorNames)", nativeQuery = true) void deleteAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId, - @Param("anchorNames") String[] anchorNames); + @Param("anchorNames") Collection<String> anchorNames); default void deleteAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, final Collection<String> anchorNames) { - deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0])); + deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames); } @Modifying diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepository.java index 8edc3f2311..9598230cdb 100755 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepository.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation. + * Copyright (C) 2021-2024 Nordix Foundation. * Modifications Copyright (C) 2020-2021 Bell Canada. * Modifications Copyright (C) 2020-2021 Pantheon.tech. * Modifications Copyright (C) 2023 TechMahindra Ltd. @@ -48,14 +48,14 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath)); } - @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", + @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND xpath IN (:xpaths)", nativeQuery = true) List<FragmentEntity> findByAnchorIdAndXpathIn(@Param("anchorId") long anchorId, - @Param("xpaths") String[] xpaths); + @Param("xpaths") Collection<String> xpaths); default List<FragmentEntity> findByAnchorAndXpathIn(final AnchorEntity anchorEntity, final Collection<String> xpaths) { - return findByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0])); + return findByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths); } @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId \n" @@ -70,58 +70,52 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, } @Query(value = "SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id " - + "WHERE dataspace_id = :dataspaceId AND xpath = ANY (:xpaths)", nativeQuery = true) + + "WHERE dataspace_id = :dataspaceId AND xpath IN (:xpaths)", nativeQuery = true) List<FragmentEntity> findByDataspaceIdAndXpathIn(@Param("dataspaceId") int dataspaceId, - @Param("xpaths") String[] xpaths); + @Param("xpaths") Collection<String> xpaths); default List<FragmentEntity> findByDataspaceAndXpathIn(final DataspaceEntity dataspaceEntity, final Collection<String> xpaths) { - return findByDataspaceIdAndXpathIn(dataspaceEntity.getId(), xpaths.toArray(new String[0])); + return findByDataspaceIdAndXpathIn(dataspaceEntity.getId(), xpaths); } @Query(value = "SELECT * FROM fragment WHERE anchor_id IN (:anchorIds)" - + " AND xpath = ANY (:xpaths)", nativeQuery = true) - List<FragmentEntity> findByAnchorIdsAndXpathIn(@Param("anchorIds") Long[] anchorIds, - @Param("xpaths") String[] xpaths); + + " AND xpath IN (:xpaths)", nativeQuery = true) + List<FragmentEntity> findByAnchorIdsAndXpathIn(@Param("anchorIds") Collection<Long> anchorIds, + @Param("xpaths") Collection<String> xpaths); @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId LIMIT 1", nativeQuery = true) Optional<FragmentEntity> findOneByAnchorId(@Param("anchorId") long anchorId); @Modifying - @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true) - void deleteByAnchorIdIn(@Param("anchorIds") long[] anchorIds); + @Query(value = "DELETE FROM fragment WHERE anchor_id IN (:anchorIds)", nativeQuery = true) + void deleteByAnchorIdIn(@Param("anchorIds") Collection<Long> anchorIds); default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) { - deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToLong(id -> id).toArray()); + deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).toList()); } @Modifying - @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true) - void deleteByAnchorIdAndXpaths(@Param("anchorId") long anchorId, @Param("xpaths") String[] xpaths); - - default void deleteByAnchorIdAndXpaths(final long anchorId, final Collection<String> xpaths) { - deleteByAnchorIdAndXpaths(anchorId, xpaths.toArray(new String[0])); - } + @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath IN (:xpaths)", nativeQuery = true) + void deleteByAnchorIdAndXpaths(@Param("anchorId") long anchorId, @Param("xpaths") Collection<String> xpaths); @Modifying - @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE ANY (:xpathPatterns)", + @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE :xpathPattern", nativeQuery = true) - void deleteByAnchorIdAndXpathLikeAny(@Param("anchorId") long anchorId, - @Param("xpathPatterns") String[] xpathPatterns); + void deleteByAnchorIdAndXpathLike(@Param("anchorId") long anchorId, @Param("xpathPattern") String xpathPattern); - default void deleteListsByAnchorIdAndXpaths(long anchorId, Collection<String> xpaths) { - deleteByAnchorIdAndXpathLikeAny(anchorId, - xpaths.stream().map(xpath -> EscapeUtils.escapeForSqlLike(xpath) + "[@%").toArray(String[]::new)); + default void deleteListByAnchorIdAndXpath(final long anchorId, final String xpath) { + deleteByAnchorIdAndXpathLike(anchorId, EscapeUtils.escapeForSqlLike(xpath) + "[@%"); } - @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", + @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath IN (:xpaths)", nativeQuery = true) List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") long anchorId, - @Param("xpaths") String[] xpaths); + @Param("xpaths") Collection<String> xpaths); default List<String> findAllXpathByAnchorAndXpathIn(final AnchorEntity anchorEntity, final Collection<String> xpaths) { - return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0])); + return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths); } @Query(value = "SELECT EXISTS(SELECT 1 FROM fragment WHERE anchor_id = :anchorId" diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java index c160fb1e38..4ca02a9c3c 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java @@ -20,6 +20,7 @@ package org.onap.cps.ri.repository; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; @@ -64,7 +65,7 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery { } final String tempTableName = tempTableCreator.createTemporaryTable( - "moduleReferencesToCheckTemp", sqlData, "module_name", "revision"); + "moduleReferencesToCheckTemp", sqlData, List.of("module_name", "revision")); return identifyNewModuleReferencesForCmHandle(tempTableName); } @@ -118,6 +119,7 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery { query.setParameter(4, dataspaceName); } + @SuppressFBWarnings(value = "VA_FORMAT_STRING_USES_NEWLINE", justification = "no \n in string just in file format") private String buildModuleReferencesSqlQuery(final String parentFragmentClause, final String childFragmentClause) { return """ WITH Fragment AS ( diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java index 9357a5c6a7..b455bc04c0 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. - * Modifications Copyright (C) 2023 Nordix Foundation + * Modifications Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,10 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte } @Modifying - @Query(value = "DELETE FROM schema_set WHERE dataspace_id = :dataspaceId AND name = ANY (:schemaSetNames)", + @Query(value = "DELETE FROM schema_set WHERE dataspace_id = :dataspaceId AND name IN (:schemaSetNames)", nativeQuery = true) void deleteByDataspaceIdAndNameIn(@Param("dataspaceId") final int dataspaceId, - @Param("schemaSetNames") final String[] schemaSetNames); + @Param("schemaSetNames") final Collection<String> schemaSetNames); /** * Delete multiple schema sets in a given dataspace. @@ -73,7 +73,7 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte */ default void deleteByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, final Collection<String> schemaSetNames) { - deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0])); + deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames); } } diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/TempTableCreator.java index cc83ab7d94..25c1491502 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/TempTableCreator.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/TempTableCreator.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation. + * Copyright (C) 2022-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,8 @@ package org.onap.cps.ri.repository; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -54,7 +52,7 @@ public class TempTableCreator { */ public String createTemporaryTable(final String prefix, final Collection<List<String>> sqlData, - final String... columnNames) { + final Collection<String> columnNames) { final String tempTableName = prefix + UUID.randomUUID().toString().replace("-", ""); final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE "); sqlStringBuilder.append(tempTableName); @@ -65,29 +63,21 @@ public class TempTableCreator { return tempTableName; } - private static void defineColumns(final StringBuilder sqlStringBuilder, final String[] columnNames) { - sqlStringBuilder.append('('); - final Iterator<String> it = Arrays.stream(columnNames).iterator(); - while (it.hasNext()) { - final String columnName = it.next(); - sqlStringBuilder.append(" "); - sqlStringBuilder.append(columnName); - sqlStringBuilder.append(" varchar NOT NULL"); - if (it.hasNext()) { - sqlStringBuilder.append(","); - } - } - sqlStringBuilder.append(")"); + private static void defineColumns(final StringBuilder sqlStringBuilder, final Collection<String> columnNames) { + final String columns = columnNames.stream() + .map(columnName -> " " + columnName + " varchar NOT NULL") + .collect(Collectors.joining(",")); + sqlStringBuilder.append('(').append(columns).append(')'); } private static void insertData(final StringBuilder sqlStringBuilder, final String tempTableName, - final String[] columnNames, + final Collection<String> columnNames, final Collection<List<String>> sqlData) { final Collection<String> sqlInserts = new HashSet<>(sqlData.size()); for (final Collection<String> rowValues : sqlData) { final Collection<String> escapedValues = - rowValues.stream().map(EscapeUtils::escapeForSqlStringLiteral).collect(Collectors.toList()); + rowValues.stream().map(EscapeUtils::escapeForSqlStringLiteral).toList(); sqlInserts.add("('" + String.join("','", escapedValues) + "')"); } sqlStringBuilder.append("INSERT INTO "); diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java index 9a11592310..831766cc9a 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java @@ -35,11 +35,7 @@ import org.springframework.stereotype.Repository; public interface YangResourceRepository extends JpaRepository<YangResourceEntity, Integer>, YangResourceNativeRepository, SchemaSetYangResourceRepository { - List<YangResourceEntity> findAllByChecksumIn(String[] checksums); - - default List<YangResourceEntity> findAllByChecksumIn(final Collection<String> checksums) { - return findAllByChecksumIn(checksums.toArray(new String[0])); - } + List<YangResourceEntity> findAllByChecksumIn(Collection<String> checksums); @Query(value = """ SELECT DISTINCT diff --git a/cps-ri/src/main/resources/changelog/changelog-master.yaml b/cps-ri/src/main/resources/changelog/changelog-master.yaml index 5909ef149b..2011655c37 100644 --- a/cps-ri/src/main/resources/changelog/changelog-master.yaml +++ b/cps-ri/src/main/resources/changelog/changelog-master.yaml @@ -20,3 +20,5 @@ databaseChangeLog: file: changelog/db/changes/01-createCPSTables.yaml - include: file: changelog/db/changes/22-fragment-id-sequence.yaml + - include: + file: changelog/db/changes/23-yang-resource-index.yaml diff --git a/cps-ri/src/main/resources/changelog/db/changes/23-yang-resource-index.yaml b/cps-ri/src/main/resources/changelog/db/changes/23-yang-resource-index.yaml new file mode 100644 index 0000000000..0dff471dc1 --- /dev/null +++ b/cps-ri/src/main/resources/changelog/db/changes/23-yang-resource-index.yaml @@ -0,0 +1,31 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2024 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========================================================= + +databaseChangeLog: + - changeSet: + author: cps + id: 23 + changes: + - createIndex: + columns: + - column: + name: module_name + - column: + name: revision + indexName: IDX_YANG_RESOURCE_MODULE_NAME_AND_REVISION + tableName: yang_resource diff --git a/cps-service/pom.xml b/cps-service/pom.xml index 2a9c75f4a6..fdd6272660 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -5,6 +5,7 @@ Modifications Copyright (C) 2021 Bell Canada. Modifications Copyright (C) 2021 Pantheon.tech Modifications Copyright (C) 2022 Deutsche Telekom AG + Modifications Copyright (C) 2024 TechMahindra Ltd. ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,7 +30,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -64,6 +65,11 @@ <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> + <!-- Disable SpotBug Rules --> + <dependency> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-annotations</artifactId> + </dependency> <dependency> <!-- For parsing JSON object --> <groupId>com.google.code.gson</groupId> @@ -139,6 +145,7 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> + <!-- T E S T D E P E N D E N C I E S --> <dependency> <groupId>org.codehaus.groovy</groupId> @@ -186,5 +193,9 @@ <artifactId>spring-kafka-test</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + </dependency> </dependencies> </project> diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index 68e1880d77..b3eff8eb26 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -322,4 +322,18 @@ public interface CpsDataService { Map<String, String> yangResourcesNameToContentMap, String targetData, FetchDescendantsOption fetchDescendantsOption); + + + /** + * Validates JSON or XML data by parsing it using the schema associated to an anchor within the given dataspace. + * Validation is performed without persisting the data. + * + * @param dataspaceName the name of the dataspace where the anchor is located. + * @param anchorName the name of the anchor used to validate the data. + * @param parentNodeXpath the xpath of the parent node where the data is to be validated. + * @param nodeData the JSON or XML data to be validated. + * @param contentType the content type of the data (e.g., JSON or XML). + */ + void validateData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + ContentType contentType); } 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 931209c998..bbfe496d85 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 @@ -178,8 +178,8 @@ public interface CpsModuleService { * an attribute key-value pair used in the WHERE clause for parent fragments. * @param childAttributes a map of attributes to filter child fragments. Each entry in this map represents * an attribute key-value pair used in the WHERE clause for child fragments. - * @return a collection of {@link ModuleReference} objects that match the given criteria. Each - * {@code ModuleReference} contains information about a module's name and revision. + * @return a collection of {@link ModuleReference} objects that match the given criteria. + * Each {@code ModuleReference} contains information about a module's name and revision. * @implNote The method assumes that both `parentAttributes` and `childAttributes` maps contain at least * one entry. The first entry from `parentAttributes` is used to filter parent fragments, * and the first entry from `childAttributes` is used to filter child fragments. 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 eed4f09bf0..b1b545be68 100644 --- 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 @@ -64,6 +64,7 @@ import org.springframework.stereotype.Service; public class CpsDataServiceImpl implements CpsDataService { private static final String ROOT_NODE_XPATH = "/"; + private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = ""; private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L; private static final String NO_DATA_NODES = "No data nodes."; @@ -358,6 +359,14 @@ public class CpsDataServiceImpl implements CpsDataService { sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp); } + @Override + public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final String nodeData, final ContentType contentType) { + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH : + CpsPathUtil.getNormalizedXpath(parentNodeXpath); + yangParser.validateData(contentType, nodeData, anchor, xpath); + } private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor, final Collection<DataNode> sourceDataNodes) { @@ -422,7 +431,8 @@ public class CpsDataServiceImpl implements CpsDataService { final String nodeData, final ContentType contentType) { if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, ""); + final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, + anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); @@ -450,7 +460,7 @@ public class CpsDataServiceImpl implements CpsDataService { if (isRootNodeXpath(xpath)) { final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, - yangResourcesNameToContentMap, ""); + yangResourcesNameToContentMap, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); diff --git a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java index 8023fbfb25..46384b5933 100644 --- a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java +++ b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java @@ -44,7 +44,7 @@ public class EventsPublisher<T> { /** * KafkaTemplate for legacy (non-cloud) events. - * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717. + * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717. */ private final KafkaTemplate<String, T> legacyKafkaEventTemplate; @@ -73,7 +73,7 @@ public class EventsPublisher<T> { /** * Generic Event publisher. - * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717. + * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717. * * @param topicName valid topic name * @param eventKey message key diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index 9859acdf0e..de57914527 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -204,18 +204,17 @@ public class DataNodeBuilder { private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode, final NormalizedNode normalizedNode) { - if (normalizedNode instanceof ChoiceNode) { - addChoiceNode(currentDataNode, (ChoiceNode) normalizedNode); - } else if (normalizedNode instanceof DataContainerNode) { - addYangContainer(currentDataNode, (DataContainerNode) normalizedNode); - } else if (normalizedNode instanceof MapNode) { - addDataNodeForEachListElement(currentDataNode, (MapNode) normalizedNode); - } else if (normalizedNode instanceof ValueNode) { - final ValueNode<NormalizedNode> valuesNode = (ValueNode) normalizedNode; - addYangLeaf(currentDataNode, valuesNode.getIdentifier().getNodeType().getLocalName(), - (Serializable) valuesNode.body()); - } else if (normalizedNode instanceof LeafSetNode) { - addYangLeafList(currentDataNode, (LeafSetNode<?>) normalizedNode); + if (normalizedNode instanceof ChoiceNode choiceNode) { + addChoiceNode(currentDataNode, choiceNode); + } else if (normalizedNode instanceof DataContainerNode dataContainerNode) { + addYangContainer(currentDataNode, dataContainerNode); + } else if (normalizedNode instanceof MapNode mapNode) { + addDataNodeForEachListElement(currentDataNode, mapNode); + } else if (normalizedNode instanceof ValueNode<?> valueNode) { + addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(), + (Serializable) valueNode.body()); + } else if (normalizedNode instanceof LeafSetNode<?> leafSetNode) { + addYangLeafList(currentDataNode, leafSetNode); } else { log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass()); } @@ -243,7 +242,7 @@ public class DataNodeBuilder { private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) { final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); - List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body()) + List<?> leafListValues = (leafSetNode.body()) .stream() .map(NormalizedNode::body) .collect(Collectors.toList()); diff --git a/cps-service/src/main/java/org/onap/cps/utils/ContentType.java b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java index f888504843..eb8e592d9b 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/ContentType.java +++ b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +21,14 @@ package org.onap.cps.utils; +import org.springframework.http.MediaType; + public enum ContentType { JSON, - XML + XML; + + public static ContentType fromString(final String contentTypeAsString) { + return contentTypeAsString.contains(MediaType.APPLICATION_XML_VALUE) ? XML : JSON; + } + } diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java index 7a6d0bb3d5..bbfb7f4d2e 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG * Modifications Copyright (C) 2023-2024 Nordix Foundation. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +22,13 @@ package org.onap.cps.utils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -33,6 +36,7 @@ import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; @@ -40,10 +44,14 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.onap.cps.spi.exceptions.DataValidationException; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.w3c.dom.DOMException; import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.xml.sax.SAXException; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -156,6 +164,89 @@ public class XmlFileUtils { return document; } + /** + * Convert a list of data maps to XML format. + * + * @param dataMaps List of data maps to convert + * @return XML string representation of the data maps + */ + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION") + public static String convertDataMapsToXml(final List<Map<String, Object>> dataMaps) { + try { + final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder(); + final Document document = documentBuilder.newDocument(); + final DocumentFragment documentFragment = document.createDocumentFragment(); + for (final Map<String, Object> dataMap : dataMaps) { + createXmlElements(document, documentFragment, dataMap); + } + return transformFragmentToString(documentFragment); + } catch (final DOMException | NullPointerException | ParserConfigurationException | TransformerException + exception) { + throw new DataValidationException( + "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception); + } + } + + private static void createXmlElements(final Document document, final Node parentNode, + final Map<String, Object> dataMap) { + for (final Map.Entry<String, Object> dataNodeMapEntry : dataMap.entrySet()) { + if (dataNodeMapEntry.getValue() instanceof List) { + appendList(document, parentNode, dataNodeMapEntry); + } else if (dataNodeMapEntry.getValue() instanceof Map) { + appendMap(document, parentNode, dataNodeMapEntry); + } else { + appendObject(document, parentNode, dataNodeMapEntry); + } + } + } + + private static void appendList(final Document document, final Node parentNode, + final Map.Entry<String, Object> dataNodeMapEntry) { + final List<Object> dataNodeMaps = (List<Object>) dataNodeMapEntry.getValue(); + if (dataNodeMaps.isEmpty()) { + final Element listElement = document.createElement(dataNodeMapEntry.getKey()); + parentNode.appendChild(listElement); + } else { + for (final Object dataNodeMap : dataNodeMaps) { + final Element listElement = document.createElement(dataNodeMapEntry.getKey()); + if (dataNodeMap == null) { + parentNode.appendChild(listElement); + } else if (dataNodeMap instanceof Map) { + createXmlElements(document, listElement, (Map<String, Object>) dataNodeMap); + } else { + listElement.appendChild(document.createTextNode(dataNodeMap.toString())); + } + parentNode.appendChild(listElement); + } + } + } + + private static void appendMap(final Document document, final Node parentNode, + final Map.Entry<String, Object> dataNodeMapEntry) { + final Element childElement = document.createElement(dataNodeMapEntry.getKey()); + createXmlElements(document, childElement, (Map<String, Object>) dataNodeMapEntry.getValue()); + parentNode.appendChild(childElement); + } + + private static void appendObject(final Document document, final Node parentNode, + final Map.Entry<String, Object> dataNodeMapEntry) { + final Element element = document.createElement(dataNodeMapEntry.getKey()); + if (dataNodeMapEntry.getValue() != null) { + element.appendChild(document.createTextNode(dataNodeMapEntry.getValue().toString())); + } + parentNode.appendChild(element); + } + + private static String transformFragmentToString(final DocumentFragment documentFragment) + throws TransformerException { + final Transformer transformer = getTransformerFactory().newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + final StringWriter writer = new StringWriter(); + final StreamResult result = new StreamResult(writer); + transformer.transform(new DOMSource(documentFragment), result); + return writer.toString(); + } + private static DocumentBuilderFactory getDocumentBuilderFactory() { if (isNewDocumentBuilderFactoryInstance) { diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java index dc23c6bc4a..168e0999d5 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java @@ -21,6 +21,9 @@ package org.onap.cps.utils; +import static org.onap.cps.utils.YangParserHelper.VALIDATE_AND_PARSE; +import static org.onap.cps.utils.YangParserHelper.VALIDATE_ONLY; + import io.micrometer.core.annotation.Timed; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -57,11 +60,12 @@ public class YangParser { final String parentNodeXpath) { final SchemaContext schemaContext = getSchemaContext(anchor); try { - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper + .parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); } catch (final DataValidationException e) { invalidateCache(anchor); } - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); } /** @@ -78,7 +82,31 @@ public class YangParser { final Map<String, String> yangResourcesNameToContentMap, final String parentNodeXpath) { final SchemaContext schemaContext = getSchemaContext(yangResourcesNameToContentMap); - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); + } + + /** + * Parses data to validate it, using the schema context for given anchor. + * + * @param anchor the anchor used for node data validation + * @param parentNodeXpath the xpath of the parent node + * @param nodeData JSON or XML data string to validate + * @param contentType the content type of the data (e.g., JSON or XML) + * @throws DataValidationException if validation fails + */ + public void validateData(final ContentType contentType, + final String nodeData, + final Anchor anchor, + final String parentNodeXpath) { + final SchemaContext schemaContext = getSchemaContext(anchor); + try { + yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY); + } catch (final DataValidationException e) { + invalidateCache(anchor); + log.error("Data validation failed for anchor: {}, xpath: {}, details: {}", anchor, parentNodeXpath, + e.getMessage()); + } + yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY); } private SchemaContext getSchemaContext(final Anchor anchor) { diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java index 597164598a..5612945ea9 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java @@ -22,6 +22,7 @@ package org.onap.cps.utils; import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.StringReader; import java.net.URISyntaxException; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLInputFactory; @@ -70,6 +72,9 @@ public class YangParserHelper { static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; static final String DATA_ROOT_NODE_TAG_NAME = "data"; + static final String DATA_VALIDATION_FAILURE_MESSAGE = "Data Validation Failed"; + static final boolean VALIDATE_ONLY = true; + static final boolean VALIDATE_AND_PARSE = false; /** * Parses data into NormalizedNode according to given schema context. @@ -83,11 +88,20 @@ public class YangParserHelper { public ContainerNode parseData(final ContentType contentType, final String nodeData, final SchemaContext schemaContext, - final String parentNodeXpath) { + final String parentNodeXpath, + final boolean validateOnly) { if (contentType == ContentType.JSON) { - return parseJsonData(nodeData, schemaContext, parentNodeXpath); + final ContainerNode validatedAndParsedJson = parseJsonData(nodeData, schemaContext, parentNodeXpath); + if (validateOnly) { + return null; + } + return validatedAndParsedJson; + } + final NormalizedNodeResult normalizedNodeResult = parseXmlData(nodeData, schemaContext, parentNodeXpath); + if (validateOnly) { + return null; } - return parseXmlData(nodeData, schemaContext, parentNodeXpath); + return buildContainerNodeFormNormalizedNodeResult(normalizedNodeResult); } private ContainerNode parseJsonData(final String jsonData, @@ -122,12 +136,13 @@ public class YangParserHelper { jsonParserStream.parse(jsonReader); } catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) { throw new DataValidationException( - "Data Validation Failed", "Failed to parse json data. " + exception.getMessage(), exception); + DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse json data. " + exception.getMessage(), exception); } return dataContainerNodeBuilder.build(); } - private ContainerNode parseXmlData(final String xmlData, + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Problem originates in 3PP code") + private NormalizedNodeResult parseXmlData(final String xmlData, final SchemaContext schemaContext, final String parentNodeXpath) { final XMLInputFactory factory = XMLInputFactory.newInstance(); @@ -164,12 +179,17 @@ public class YangParserHelper { } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException | ParserConfigurationException | TransformerException exception) { throw new DataValidationException( - "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception); + DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse xml data: " + exception.getMessage(), exception); } + return normalizedNodeResult; + } + + private ContainerNode buildContainerNodeFormNormalizedNodeResult(final NormalizedNodeResult normalizedNodeResult) { + final DataContainerChild dataContainerChild = - (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); + (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); final YangInstanceIdentifier.NodeIdentifier nodeIdentifier = - new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); + new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build(); } @@ -181,12 +201,12 @@ public class YangParserHelper { private static Map<String, Object> getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, final SchemaContext schemaContext) { - final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); + final List<String> xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), new ArrayList<>()); } - private static String[] xpathToNodeIdSequence(final String xpath) { + private static List<String> xpathToNodeIdSequence(final String xpath) { try { return CpsPathUtil.getXpathNodeIdSequence(xpath); } catch (final PathParsingException pathParsingException) { @@ -196,17 +216,16 @@ public class YangParserHelper { } private static Map<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( - final String[] xpathNodeIdSequence, + final List<String> xpathNodeIdSequence, final Collection<? extends DataSchemaNode> dataSchemaNodes, final Collection<QName> dataSchemaNodeIdentifiers) { - final String currentXpathNodeId = xpathNodeIdSequence[0]; + final String currentXpathNodeId = xpathNodeIdSequence.get(0); final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream() .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName())) .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName()); - if (xpathNodeIdSequence.length <= 1) { - final Map<String, Object> dataSchemaNodeAndIdentifiers = - new HashMap<>(); + if (xpathNodeIdSequence.size() <= 1) { + final Map<String, Object> dataSchemaNodeAndIdentifiers = new HashMap<>(); dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); return dataSchemaNodeAndIdentifiers; @@ -217,13 +236,11 @@ public class YangParserHelper { ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), dataSchemaNodeIdentifiers); } - throw schemaNodeNotFoundException(xpathNodeIdSequence[1]); + throw schemaNodeNotFoundException(xpathNodeIdSequence.get(1)); } - private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) { - final String[] nextXpathNodeIdSequence = new String[xpathNodeIdSequence.length - 1]; - System.arraycopy(xpathNodeIdSequence, 1, nextXpathNodeIdSequence, 0, nextXpathNodeIdSequence.length); - return nextXpathNodeIdSequence; + private static List<String> getNextLevelXpathNodeIdSequence(final List<String> xpathNodeIdSequence) { + return xpathNodeIdSequence.subList(1, xpathNodeIdSequence.size()); } private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) { 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 9846b30158..8c208a1cf8 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 @@ -546,6 +546,20 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor2, '/', DELETE, observedTimestamp) } + def "Validating #scenario when dry run is enabled."() { + given: 'schema set for given anchors and dataspace references bookstore model' + setupSchemaSetMocks('bookstore.yang') + when: 'validating the data with the given parameters' + objectUnderTest.validateData(dataspaceName, anchorName, parentNodeXpath, data,contentType) + then: 'the appropriate yang parser method is invoked with correct parameters' + yangParser.validateData(contentType, data, anchor, xpath) + where: 'the following parameters were used' + scenario | parentNodeXpath | xpath | contentType | data + 'JSON data with root node xpath' | '/' | '' | ContentType.JSON | '{"bookstore":{"bookstore-name":"Easons"}}' + 'JSON data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.JSON | '{"bookstore-name":"Easons"}' + 'XML data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.XML | '<bookstore-name>Easons</bookstore-name>' + } + def 'Start session.'() { when: 'start session method is called' objectUnderTest.startSession() diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 05c8983fc2..9f3456280e 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -171,6 +171,6 @@ class E2ENetworkSliceSpec extends Specification { expect: 'schema context is built with no exception indicating the schema set being valid '
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
and: 'data is parsed with no exception indicating the model match'
- new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null
+ new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '', false) != null
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index e305abee86..f028d5d5d9 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -35,6 +35,7 @@ class DataNodeBuilderSpec extends Specification { def objectUnderTest = new DataNodeBuilder() def yangParserHelper = new YangParserHelper() + def validateAndParse = false def expectedLeavesByXpathMap = [ '/test-tree' : [], @@ -60,7 +61,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('test-tree.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -80,7 +81,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse) when: 'the container node is converted to a data node with parent node xpath defined' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -96,7 +97,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node ' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -129,7 +130,7 @@ class DataNodeBuilderSpec extends Specification { def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']" and: 'the json data fragment parsed into container node object for given parent node xpath' def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse) when: 'the container node is converted to a data node with given parent node xpath' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() then: 'the resulting data node represents a child of augmentation node' @@ -144,7 +145,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data fragment parsed into container node object' def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -162,7 +163,7 @@ class DataNodeBuilderSpec extends Specification { and: 'parent node xpath referencing parent of list element' def parentNodeXpath = '/test-tree' and: 'the json data fragment (list element) parsed into container node object' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse) when: 'the container node is converted to a data node collection' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() def resultXpaths = result.collect { it.getXpath() } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy new file mode 100644 index 0000000000..cada33ef06 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy @@ -0,0 +1,37 @@ +/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 TechMahindra Ltd
+ * ================================================================================
+ * 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 spock.lang.Specification;
+import org.springframework.http.MediaType
+
+
+class ContentTypeSpec extends Specification {
+
+ def 'Should return correct ContentType based on given input.'() {
+ given: 'contentType fromString method converts the input string as expectedContentType'
+ ContentType.fromString(contentTypeString) == expectedContentType
+ where:
+ contentTypeString || expectedContentType
+ MediaType.APPLICATION_XML_VALUE || ContentType.XML
+ MediaType.APPLICATION_JSON_VALUE || ContentType.JSON
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy index dc6027de25..9a932c9279 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG * Modifications Copyright (c) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +22,17 @@ package org.onap.cps.utils import org.onap.cps.TestUtils +import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.w3c.dom.DOMException import org.xml.sax.SAXParseException import spock.lang.Specification +import static org.onap.cps.utils.XmlFileUtils.convertDataMapsToXml + class XmlFileUtilsSpec extends Specification { - def 'Parse a valid xml content #scenario'(){ + def 'Parse a valid xml content #scenario'() { given: 'YANG model schema context' def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() @@ -36,13 +41,13 @@ class XmlFileUtilsSpec extends Specification { then: 'the result xml is wrapped by root node defined in YANG schema' assert parsedXmlContent == expectedOutput where: - scenario | xmlData || expectedOutput - 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><class> </class>' || '<?xml version="1.0" encoding="UTF-8"?><stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><class> </class></stores>' - 'with root data node' | '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>' || '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>' - 'no xml header' | '<stores><class> </class></stores>' || '<stores><class> </class></stores>' + scenario | xmlData || expectedOutput + 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><class> </class>' || '<?xml version="1.0" encoding="UTF-8"?><stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><class> </class></stores>' + 'with root data node' | '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>' || '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>' + 'no xml header' | '<stores><class> </class></stores>' || '<stores><class> </class></stores>' } - def 'Parse a invalid xml content'(){ + def 'Parse a invalid xml content'() { given: 'YANG model schema context' def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() @@ -68,4 +73,56 @@ class XmlFileUtilsSpec extends Specification { 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || '<?xml version="1.0" encoding="UTF-8"?><branch xmlns="org:onap:cps:test:test-tree"><name>Branch</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>' } -} + def 'Convert data maps to XML #scenario'() { + when: 'data maps are converted to XML' + def result = convertDataMapsToXml(dataMaps) + then: 'the result contains the expected XML' + assert result == expectedXmlOutput + where: + scenario | dataMaps || expectedXmlOutput + 'single XML branch' | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': ['Sparrow', 'Owl']]]]] || '<branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Owl</birds></nest></branch>' + 'nested XML branch' | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>' + 'list of branch within a test tree' | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch><branch><name>Right</name><nest><name>Big</name><birds>Owl</birds></nest></branch></test-tree>' + 'list of birds under a nest' | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]] || '<nest><name>Small</name><birds>Sparrow</birds></nest>' + } + + def 'Convert data maps to XML with null or empty maps and lists'() { + when: 'data maps with empty content are converted to XML' + def result = convertDataMapsToXml(dataMaps) + then: 'the result contains the expected XML or handles nulls correctly' + assert result == expectedXmlOutput + where: + scenario | dataMaps || expectedXmlOutput + 'null entry in map' | [['branch': []]] || '<branch/>' + 'XML Content list is empty' | [['nest': ['name': 'Small', 'birds': [null]]]] || '<nest><name>Small</name><birds/></nest>' + 'XML with mixed content in list' | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': [null, 'Sparrow']]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/><birds>Sparrow</birds></nest></branch>' + 'list with null object' | [['branch': [name: 'Left', nest: [name: 'Small', birds: [null]]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/></nest></branch>' + 'list containing null values' | [['branch': [null, null, null]]] || '<branch/><branch/><branch/>' + 'nested map with null values' | [['test-tree': [branch: [name: 'Left', nest: null]]]] || '<test-tree><branch><name>Left</name><nest/></branch></test-tree>' + 'mixed list with null values' | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': [null, 'Sparrow', null]]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/><birds>Sparrow</birds><birds/></nest></branch>' + } + + def 'Converting data maps to xml with no data'() { + given: 'A list of maps where entry is null' + def dataMapWithNull = [null] + when: 'convert the dataMaps to XML' + convertDataMapsToXml(dataMapWithNull) + then: 'a validation exception is thrown' + def exception = thrown(DataValidationException) + and: 'the cause is a null pointer exception' + assert exception.cause instanceof NullPointerException + } + + def 'Converting data maps to xml with document syntax error'() { + given: 'A list of maps with an invalid entry' + def dataMap = [['invalid<tag>': 'value']] + when: 'convert the dataMaps to XML' + convertDataMapsToXml(dataMap) + then: 'a validation exception is thrown' + def exception = thrown(DataValidationException) + and: 'the cause is a document object model exception' + assert exception.cause instanceof DOMException + + } + +}
\ No newline at end of file diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy index 073383113d..e1490c28ab 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy @@ -30,6 +30,8 @@ import spock.lang.Specification class YangParserHelperSpec extends Specification { def objectUnderTest = new YangParserHelper() + def validateOnly = true + def validateAndParse = false def 'Parsing a valid multicontainer Json String.'() { given: 'a yang model (file)' @@ -38,7 +40,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'the json data is parsed' - def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '') + def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) then: 'a ContainerNode holding collection of normalized nodes is returned' result.body().getAt(index) instanceof NormalizedNode == true then: 'qualified name of children created is as expected' @@ -56,7 +58,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'the data is parsed' - NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '') + NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '', validateAndParse) then: 'the result is a normalized node of the correct type' if (revision) { result.identifier.nodeType == QName.create(namespace, revision, localName) @@ -74,7 +76,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'invalid data is parsed' - objectUnderTest.parseData(contentType, invalidData, schemaContext, '') + objectUnderTest.parseData(contentType, invalidData, schemaContext, '', validateAndParse) then: 'an exception is thrown' thrown(DataValidationException) where: 'the following invalid data is provided' @@ -92,7 +94,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'json string is parsed' - def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath) + def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath, validateAndParse) then: 'a ContainerNode holding collection of normalized nodes is returned' result.body().getAt(0) instanceof NormalizedNode == true then: 'result represents a node of expected type' @@ -112,7 +114,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'json string is parsed' - objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath) + objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath, validateAndParse) then: 'expected exception is thrown' thrown(DataValidationException) where: @@ -129,7 +131,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'malformed json string is parsed' - objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '') + objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '', validateAndParse) then: 'an exception is thrown' thrown(DataValidationException) where: 'the following malformed json is provided' @@ -145,7 +147,7 @@ class YangParserHelperSpec extends Specification { and: 'some json data with space in the array elements' def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json') when: 'that json data is parsed' - objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '') + objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '', validateAndParse) then: 'no exception thrown' noExceptionThrown() } @@ -162,5 +164,22 @@ class YangParserHelperSpec extends Specification { 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories'] } + def 'Validating #scenario xpath String.'() { + given: 'a data model (file) is provided' + def fileData = TestUtils.getResourceFileContent(contentFile) + and: 'the schema context is built for that data model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'the data is parsed to be validated' + objectUnderTest.parseData(contentType, fileData, schemaContext, parentNodeXpath, validateOnly) + then: 'no exception is thrown' + noExceptionThrown() + where: + scenario | parentNodeXpath | contentFile | contentType + 'JSON without parent node' | '' | 'bookstore.json' | ContentType.JSON + 'JSON with parent node' | '/bookstore' | 'bookstore-categories-data.json' | ContentType.JSON + 'XML without parent node' | '' | 'bookstore.xml' | ContentType.XML + 'XML with parent node' | '/bookstore' | 'bookstore-categories-data.xml' | ContentType.XML + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy index 18d0502e30..6c52becbe1 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy @@ -26,7 +26,6 @@ import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.Anchor import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode import org.opendaylight.yangtools.yang.model.api.SchemaContext import spock.lang.Specification @@ -47,6 +46,8 @@ class YangParserSpec extends Specification { def containerNodeFromYangUtils = Mock(ContainerNode) def noParent = '' + def validateOnly = true + def validateAndParse = false def setup() { mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet @@ -55,7 +56,7 @@ class YangParserSpec extends Specification { def 'Parsing data.'() { given: 'the yang parser (utility) always returns a container node' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils when: 'parsing some json data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'the schema source set for the correct dataspace and schema set is retrieved form the cache' @@ -68,7 +69,7 @@ class YangParserSpec extends Specification { def 'Parsing data with exception on first attempt.'() { given: 'the yang parser throws an exception on the first attempt only' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils when: 'attempt to parse some data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'the cache is cleared for the correct dataspace and schema' @@ -79,7 +80,7 @@ class YangParserSpec extends Specification { def 'Parsing data with exception on all attempts.'() { given: 'the yang parser always throws an exception' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) } when: 'attempt to parse some data' objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'a data validation exception is thrown' @@ -94,9 +95,46 @@ class YangParserSpec extends Specification { when: 'parsing some json data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent) then: 'the yang parser helper always returns a container node' - 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils and: 'the result is the same container node as return from yang utils' assert result == containerNodeFromYangUtils } + def 'Validating #scenario data using Yang parser with cache retrieval.'() { + given: 'the yang parser (utility) is set up and schema context is available' + mockYangParserHelper.parseData(contentType, 'some json', mockSchemaContext, noParent, validateOnly) + when: 'attempt to parse data with no parent node xpath' + objectUnderTest.validateData(contentType, 'some json or xml data', anchor, noParent) + then: 'the correct schema set is retrieved from the cache for the dataspace and schema' + 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet + and: 'no cache entries are removed during validation' + 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_) + where: + scenario | contentType + 'JSON' | ContentType.JSON + 'XML' | ContentType.XML + } + + def 'Validating data when parsing fails on first attempt and recovers.'() { + given: 'the Yang parser throws an exception on the first attempt but succeeds on the second' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } >> null + when: 'attempting to parse JSON data' + objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent) + then: 'the cache is cleared for the correct dataspace and schema after the first failure' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + and: 'no exceptions are thrown after the second attempt' + noExceptionThrown() + } + + def 'Validating data with repeated parsing failures leading to exception.'() { + given: 'the yang parser throws an exception on the first attempt only' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } + when: 'attempting to parse JSON data' + objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the cache is cleared for the correct dataspace and schema after the failure' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + } + } diff --git a/cps-service/src/test/resources/bookstore-categories-data.json b/cps-service/src/test/resources/bookstore-categories-data.json new file mode 100644 index 0000000000..7dc22b17f7 --- /dev/null +++ b/cps-service/src/test/resources/bookstore-categories-data.json @@ -0,0 +1,49 @@ +{ + "categories": [ + { + "code": "01/1", + "name": "SciFi", + "books": [ + { + "authors": [ + "Iain M. Banks" + ], + "lang": "en/it", + "price": "895", + "pub_year": "1994", + "title": "Feersum Endjinn/Endjinn Feersum" + }, + { + "authors": [ + "Ursula K. Le Guin", + "Joe Haldeman", + "Orson Scott Card", + "david Brin", + "Rober Silverberg", + "Dan Simmons", + "Greg Bear" + ], + "lang": "en", + "price": "1099", + "pub_year": "1999", + "title": "Far Horizons" + } + ] + }, + { + "name": "kids", + "code": "02", + "books": [ + { + "authors": [ + "Philip Pullman" + ], + "lang": "en", + "price": "699", + "pub_year": "1995", + "title": "The Golden Compass" + } + ] + } + ] +}
\ No newline at end of file diff --git a/cps-service/src/test/resources/bookstore-categories-data.xml b/cps-service/src/test/resources/bookstore-categories-data.xml new file mode 100644 index 0000000000..c8592c1f90 --- /dev/null +++ b/cps-service/src/test/resources/bookstore-categories-data.xml @@ -0,0 +1,14 @@ +<?xml version='1.0' encoding='UTF-8'?> +<categories> + <code>1</code> + <name>SciFi</name> + <books> + <title>2001: A Space Odyssey</title> + <lang>en</lang> + <authors> + Iain M. Banks + </authors> + <pub_year>1994</pub_year> + <price>895</price> + </books> +</categories>
\ No newline at end of file diff --git a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang index 32517398a3..2dabd79e8e 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang @@ -36,7 +36,7 @@ module cps-cavsta-onap-internal { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef Tac { diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang index c16a682512..2401409443 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang @@ -34,7 +34,7 @@ module cps-ran-inventory { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef Mcc { diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang index 5fd292a99d..3223b15e65 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang @@ -43,14 +43,14 @@ module cps-ran-schema-model { description "Added support for OOF PCI SON Use case"; reference - "https://wiki.onap.org/display/DW/CPS+APIs"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16456851/CPS+APIs"; } revision 2021-01-28 { description "CPS RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef usageState { diff --git a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang index 5065659307..a4612e73fb 100755 --- a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang +++ b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang @@ -43,7 +43,7 @@ module ran-network { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef usageState { diff --git a/csit/plans/cps/setup.sh b/csit/plans/cps/setup.sh index 036f14b82f..00ed52a7ef 100755 --- a/csit/plans/cps/setup.sh +++ b/csit/plans/cps/setup.sh @@ -77,4 +77,4 @@ check_health $DMI_HOST:$DMI_PORT 'dmi-plugin' ###################### ROBOT Configurations ########################## # Pass variables required for Robot test suites in ROBOT_VARIABLES -ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure" +ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_VERSION:$DMI_VERSION -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure" diff --git a/csit/plans/cps/test.properties b/csit/plans/cps/test.properties index 396bdd3009..e7b9519c2d 100644 --- a/csit/plans/cps/test.properties +++ b/csit/plans/cps/test.properties @@ -21,7 +21,7 @@ DMI_SERVICE_URL=http://$LOCAL_IP:$DMI_PORT DOCKER_REPO=nexus3.onap.org:10003 CPS_VERSION=latest -DMI_VERSION=1.6.0-SNAPSHOT-latest +DMI_VERSION=1.5.1-SNAPSHOT-latest ADVISED_MODULES_SYNC_SLEEP_TIME_MS=2000 CMHANDLE_DATA_SYNC_SLEEP_TIME_MS=2000 diff --git a/csit/tests/cps-model-sync/cps-model-sync.robot b/csit/tests/cps-model-sync/cps-model-sync.robot index 514076f085..b4e61b30d8 100644 --- a/csit/tests/cps-model-sync/cps-model-sync.robot +++ b/csit/tests/cps-model-sync/cps-model-sync.robot @@ -78,17 +78,6 @@ Get CM Handle details and confirm it has been updated. END END -Delete cm handle - ${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=${deletePayload} - Should Be Equal As Strings ${response.status_code} 200 - -Get cm handle details and confirm it has been deleted - ${uri}= Set Variable ${ncmpBasePath}/v1/ch/CmHandleForDelete - ${headers}= Create Dictionary Authorization=${auth} - ${response}= GET On Session CPS_URL ${uri} headers=${headers} expected_status=404 - Check if ietfYang-PNFDemo is READY ${uri}= Set Variable ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo ${headers}= Create Dictionary Authorization=${auth} @@ -107,6 +96,17 @@ Get modules for registered data node END END +Delete cm handle + ${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=${deletePayload} + Should Be Equal As Strings ${response.status_code} 200 + +Get cm handle details and confirm it has been deleted + ${uri}= Set Variable ${ncmpBasePath}/v1/ch/CmHandleForDelete + ${headers}= Create Dictionary Authorization=${auth} + ${response}= GET On Session CPS_URL ${uri} headers=${headers} expected_status=404 + *** Keywords *** Is CM Handle READY @@ -125,4 +125,4 @@ Count Items In JSON Response [Arguments] ${response} ${json_data}= Evaluate json.loads('${response.content.decode("utf-8")}') json ${number_of_items}= Get Length ${json_data} - RETURN ${number_of_items}
\ No newline at end of file + RETURN ${number_of_items} diff --git a/docker-compose/config/endurance.env b/docker-compose/config/endurance.env new file mode 100644 index 0000000000..0ca1a1149a --- /dev/null +++ b/docker-compose/config/endurance.env @@ -0,0 +1,35 @@ +DB_CONTAINER_NAME=endurance-dbpostgresql +DB_PORT=5433 + +NGINX_CONTAINER_NAME=endurance-nginx-loadbalancer +CPS_CORE_PORT=8884 + +ZOOKEEPER_CONTAINER_NAME=endurance-zookeeper +ZOOKEEPER_PORT=2182 + +KAFKA_CONTAINER_NAME=endurance-kafka +KAFKA_PORT=9093 + +NCMP_DMI_PLUGIN_CONTAINER_NAME=endurance-ncmp-dmi-plugin +DMI_PORT=8786 + +NCMP_DMI_PLUGIN_DEMO_AND_CSIT_STUB_CONTAINER_NAME=endurance-ncmp-dmi-plugin-demo-and-csit-stub +DMI_DEMO_STUB_PORT=8787 + +POLICY_EXECUTOR_STUB_CONTAINER_NAME=endurance-policy-executor-stub +POLICY_EXECUTOR_STUB_PORT=8788 + +PROMETHEUS_CONTAINER_NAME=endurance-prometheus +PROMETHEUS_PORT=9091 + +GRAFANA_CONTAINER_NAME=endurance-grafana +GRAFANA_PORT=3001 + +KAFKA_UI_CONTAINER_NAME=endurance-kafka-ui +KAFKA_UI_PORT=8090 + +JAEGER_SERVICE_CONTAINER_NAME=endurance-jaeger-service +JAEGER_SERVICE_PORT=16687 + +CPS_NCMP_CACHES_CLUSTER_NAME=endurance-cps-and-ncmp-common-cache-cluster +CPS_NCMP_INSTANCE_CONFIG_NAME=endurance-cps-and-ncmp-hazelcast-instance-config
\ No newline at end of file diff --git a/docker-compose/config/nginx/nginx.conf b/docker-compose/config/nginx/nginx.conf index 61fed515c3..6e9d102fed 100644 --- a/docker-compose/config/nginx/nginx.conf +++ b/docker-compose/config/nginx/nginx.conf @@ -21,8 +21,7 @@ http { # Add more server entries here for scaling or load balancing upstream cps-and-ncmp { least_conn; - server docker-compose-cps-and-ncmp-1:8080; - server docker-compose-cps-and-ncmp-2:8080; + server cps-and-ncmp:8080; } server { diff --git a/docker-compose/config/prometheus.yml b/docker-compose/config/prometheus.yml index 89af4a6800..e1aa4763d9 100644 --- a/docker-compose/config/prometheus.yml +++ b/docker-compose/config/prometheus.yml @@ -3,10 +3,12 @@ global: evaluation_interval: 5s scrape_configs: - - job_name: 'cps-and-ncmp' - metrics_path: '/actuator/prometheus' - scrape_interval: 5s - static_configs: - - targets: - - 'docker-compose-cps-and-ncmp-1:8080' - - 'docker-compose-cps-and-ncmp-2:8080' +- job_name: 'cps-and-ncmp' + metrics_path: '/actuator/prometheus' + scrape_interval: 5s + static_configs: + # If you want to inject Prometheus into particular containers, the targets should be provided similar to the following examples: + # - 'docker-compose-cps-and-ncmp-1:8080' + # - 'docker-compose-cps-and-ncmp-2:8080' + - targets: + - 'cps-and-ncmp:8080'
\ No newline at end of file diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 1e47d47382..feb58d849d 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -23,9 +23,12 @@ services: ### docker-compose --profile dmi-stub --profile tracing up -d -> run CPS with stubbed dmi-plugin (for open telemetry tracing testing make ONAP_TRACING_ENABLED "true" later "http://localhost:16686" can be accessed from browser) ### docker-compose --profile dmi-stub --profile policy-executor-stub up -d -> run CPS with stubbed dmi-plugin and policy executor stub (for policy executor service testing make POLICY_SERVICE_ENABLED "true") ### to disable notifications make notification.enabled to false & comment out kafka/zookeeper services ### + ### DEBUG: Look for '### DEBUG' comments to enable CPS-NCMP debugging + ### docker-compose --profile dmi-stub --project-name endurance --env-file config/endurance.env up -d -> run CPS with stubbed dmi-plugin for endurance testing + ### docker-compose --profile dmi-stub --project-name endurance down --volumes dbpostgresql: - container_name: dbpostgresql + container_name: ${DB_CONTAINER_NAME:-dbpostgresql} image: postgres:14.1-alpine ports: - ${DB_PORT:-5432}:5432 @@ -60,21 +63,26 @@ services: ONAP_OTEL_SAMPLER_JAEGER_REMOTE_ENDPOINT: http://jaeger-service:14250 ONAP_OTEL_EXPORTER_ENDPOINT: http://jaeger-service:4317 POLICY_SERVICE_ENABLED: 'false' + POLICY_SERVICE_DEFAULT_DECISION: 'deny from env' + JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0" + ### DEBUG: Uncomment next line to enable java debugging + ### DEBUG: JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 restart: unless-stopped depends_on: - dbpostgresql deploy: + ### DEBUG: For easier debugging use just 1 instance (also update docker-compose/config/nginx/nginx.conf !) replicas: 2 resources: - reservations: - cpus: '2' - memory: 2G limits: cpus: '3' - memory: 3G + memory: 2G + ### DEBUG: Uncomment next 2 lines to enable java debugging (ensure 'ports' aligns with 'deploy') + ### DEBUG ports: + ### DEBUG - ${CPS_CORE_DEBUG_PORT:-5005}:5005 nginx: - container_name: nginx-loadbalancer + container_name: ${NGINX_CONTAINER_NAME:-nginx-loadbalancer} image: nginx:latest ports: - ${CPS_CORE_PORT:-8883}:80 @@ -87,17 +95,17 @@ services: ### if kafka is not required comment out zookeeper and kafka ### zookeeper: image: confluentinc/cp-zookeeper:6.2.1 - container_name: zookeeper + container_name: ${ZOOKEEPER_CONTAINER_NAME:-zookeeper} ports: - - '2181:2181' + - ${ZOOKEEPER_PORT:-2181}:2181 environment: ZOOKEEPER_CLIENT_PORT: 2181 kafka: image: confluentinc/cp-kafka:6.2.1 - container_name: kafka + container_name: ${KAFKA_CONTAINER_NAME:-kafka} ports: - - '9092:9092' + - ${KAFKA_PORT:-9092}:9092 depends_on: - zookeeper environment: @@ -108,7 +116,7 @@ services: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 ncmp-dmi-plugin: - container_name: ncmp-dmi-plugin + container_name: ${NCMP_DMI_PLUGIN_CONTAINER_NAME:-ncmp-dmi-plugin} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${DMI_VERSION:-1.6.0-SNAPSHOT-latest} ports: - ${DMI_PORT:-8783}:8080 @@ -133,7 +141,7 @@ services: - dmi-service ncmp-dmi-plugin-demo-and-csit-stub: - container_name: ncmp-dmi-plugin-demo-and-csit-stub + container_name: ${NCMP_DMI_PLUGIN_DEMO_AND_CSIT_STUB_CONTAINER_NAME:-ncmp-dmi-plugin-demo-and-csit-stub} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/dmi-plugin-demo-and-csit-stub:${DMI_DEMO_STUB_VERSION:-latest} ports: - ${DMI_DEMO_STUB_PORT:-8784}:8092 @@ -141,7 +149,7 @@ services: KAFKA_BOOTSTRAP_SERVER: kafka:29092 NCMP_CONSUMER_GROUP_ID: ncmp-group NCMP_ASYNC_M2M_TOPIC: ncmp-async-m2m - MODULE_INITIAL_PROCESSING_DELAY_MS: 0 + MODULE_INITIAL_PROCESSING_DELAY_MS: 120000 MODULE_REFERENCES_DELAY_MS: 100 MODULE_RESOURCES_DELAY_MS: 1000 READ_DATA_FOR_CM_HANDLE_DELAY_MS: 300 @@ -152,19 +160,19 @@ services: - dmi-service policy-executor-stub: - container_name: policy-executor-stub + container_name: ${POLICY_EXECUTOR_STUB_CONTAINER_NAME:-policy-executor-stub} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/policy-executor-stub:latest ports: - - 8785:8093 + - ${POLICY_EXECUTOR_STUB_PORT:-8785}:8093 restart: unless-stopped profiles: - policy-executor-stub prometheus: - container_name: prometheus + container_name: ${PROMETHEUS_CONTAINER_NAME:-prometheus} image: prom/prometheus:latest ports: - - 9090:9090 + - ${PROMETHEUS_PORT:-9090}:9090 restart: always volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml @@ -174,12 +182,12 @@ services: grafana: image: grafana/grafana-oss:latest user: "" - container_name: grafana + container_name: ${GRAFANA_CONTAINER_NAME:-grafana} depends_on: prometheus: condition: service_started ports: - - 3000:3000 + - ${GRAFANA_PORT:-3000}:3000 volumes: - ./config/grafana/provisioning/:/etc/grafana/provisioning/ - ./config/grafana/jvm-micrometer-dashboard.json:/var/lib/grafana/dashboards/jvm-micrometer-dashboard.json @@ -191,10 +199,10 @@ services: - monitoring kafka-ui: - container_name: kafka-ui + container_name: ${KAFKA_UI_CONTAINER_NAME:-kafka-ui} image: provectuslabs/kafka-ui:latest ports: - - 8089:8080 + - ${KAFKA_UI_PORT:-8089}:8080 environment: DYNAMIC_CONFIG_ENABLED: 'true' KAFKA_CLUSTERS_0_NAME: 'cps-kafka-local' @@ -203,10 +211,10 @@ services: - monitoring jaeger-service: - container_name: jaeger-service + container_name: ${JAEGER_SERVICE_CONTAINER_NAME:-jaeger-service} image: jaegertracing/all-in-one:latest ports: - - 16686:16686 + - ${JAEGER_SERVICE_PORT:-16686}:16686 restart: unless-stopped profiles: - tracing diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 74b5234828..3b6bd43d6c 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -9,16 +9,16 @@ info: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0 title: ONAP Open API v3 Configuration Persistence Service - version: 3.5.2 + version: 3.5.4 servers: - - url: /cps/api +- url: /cps/api security: - - basicAuth: [] +- basicAuth: [] tags: - - description: cps Admin - name: cps-admin - - description: cps Data - name: cps-data +- description: cps Admin + name: cps-admin +- description: cps Data + name: cps-data paths: /v1/dataspaces: post: @@ -26,13 +26,13 @@ paths: description: Create a new dataspace operationId: createDataspace parameters: - - description: dataspace-name - in: query - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: dataspace-name + in: query + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "201": content: @@ -83,29 +83,29 @@ paths: description: Internal Server Error summary: Create a dataspace tags: - - cps-admin + - cps-admin /{apiVersion}/dataspaces: delete: description: Delete a dataspace operationId: deleteDataspace parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: query - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: query + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "204": content: {} @@ -152,19 +152,19 @@ paths: description: Internal Server Error summary: Delete a dataspace tags: - - cps-admin + - cps-admin /v2/dataspaces: post: description: Create a new dataspace operationId: createDataspaceV2 parameters: - - description: dataspace-name - in: query - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: dataspace-name + in: query + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "201": description: Created without response body @@ -210,22 +210,22 @@ paths: description: Internal Server Error summary: Create a dataspace tags: - - cps-admin + - cps-admin /{apiVersion}/admin/dataspaces: get: description: Read all dataspaces operationId: getAllDataspaces parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string responses: "200": content: @@ -267,29 +267,29 @@ paths: description: Internal Server Error summary: Get all dataspaces tags: - - cps-admin + - cps-admin /{apiVersion}/admin/dataspaces/{dataspace-name}: get: description: Read a dataspace given a dataspace name operationId: getDataspace parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "200": content: @@ -329,34 +329,34 @@ paths: description: Internal Server Error summary: Get a dataspace tags: - - cps-admin + - cps-admin /v1/dataspaces/{dataspace-name}/anchors: post: deprecated: true description: Create a new anchor in the given dataspace operationId: createAnchor parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: query - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string - - description: anchor-name - in: query - name: anchor-name - required: true - schema: - example: my-anchor - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: query + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string + - description: anchor-name + in: query + name: anchor-name + required: true + schema: + example: my-anchor + type: string responses: "201": content: @@ -407,33 +407,33 @@ paths: description: Internal Server Error summary: Create an anchor tags: - - cps-admin + - cps-admin /v2/dataspaces/{dataspace-name}/anchors: post: description: Create a new anchor in the given dataspace operationId: createAnchorV2 parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: query - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string - - description: anchor-name - in: query - name: anchor-name - required: true - schema: - example: my-anchor - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: query + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string + - description: anchor-name + in: query + name: anchor-name + required: true + schema: + example: my-anchor + type: string responses: "201": description: Created without response body @@ -479,29 +479,29 @@ paths: description: Internal Server Error summary: Create an anchor tags: - - cps-admin + - cps-admin /{apiVersion}/dataspaces/{dataspace-name}/anchors: get: description: "Read all anchors, given a dataspace" operationId: getAnchors parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "200": content: @@ -543,36 +543,36 @@ paths: description: Internal Server Error summary: Get anchors tags: - - cps-admin + - cps-admin /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}: delete: description: Delete an anchor given an anchor name and a dataspace operationId: deleteAnchor parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string responses: "204": content: {} @@ -609,35 +609,35 @@ paths: description: Internal Server Error summary: Delete an anchor tags: - - cps-admin + - cps-admin get: description: Read an anchor given an anchor name and a dataspace operationId: getAnchor parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string responses: "200": content: @@ -677,27 +677,27 @@ paths: description: Internal Server Error summary: Get an anchor tags: - - cps-admin + - cps-admin /v1/dataspaces/{dataspace-name}/schema-sets: post: deprecated: true description: Create a new schema set in the given dataspace operationId: createSchemaSet parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: query - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: query + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string requestBody: content: multipart/form-data: @@ -754,26 +754,26 @@ paths: description: Internal Server Error summary: Create a schema set tags: - - cps-admin + - cps-admin /v2/dataspaces/{dataspace-name}/schema-sets: post: description: Create a new schema set in the given dataspace operationId: createSchemaSetV2 parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: query - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: query + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string requestBody: content: multipart/form-data: @@ -825,29 +825,29 @@ paths: description: Internal Server Error summary: Create a schema set tags: - - cps-admin + - cps-admin /{apiVersion}/dataspaces/{dataspace-name}/schema-sets: get: description: "Read all schema sets, given a dataspace" operationId: getSchemaSets parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string responses: "200": content: @@ -889,36 +889,36 @@ paths: description: Internal Server Error summary: Get schema sets tags: - - cps-admin + - cps-admin /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: delete: description: Delete a schema set given a schema set name and a dataspace operationId: deleteSchemaSet parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: path - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: path + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string responses: "204": content: {} @@ -965,35 +965,35 @@ paths: description: Internal Server Error summary: Delete a schema set tags: - - cps-admin + - cps-admin get: description: Read a schema set given a schema set name and a dataspace operationId: getSchemaSet parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: schema-set-name - in: path - name: schema-set-name - required: true - schema: - example: my-schema-set - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: schema-set-name + in: path + name: schema-set-name + required: true + schema: + example: my-schema-set + type: string responses: "200": content: @@ -1033,7 +1033,7 @@ paths: description: Internal Server Error summary: Get a schema set tags: - - cps-admin + - cps-admin /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: get: deprecated: true @@ -1041,40 +1041,40 @@ paths: anchor and dataspace operationId: getNodeByDataspaceAndAnchor parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: include-descendants - in: query - name: include-descendants - required: false - schema: - default: false - example: false - type: boolean + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: include-descendants + in: query + name: include-descendants + required: false + schema: + default: false + example: false + type: boolean responses: "200": content: @@ -1118,7 +1118,7 @@ paths: description: Internal Server Error summary: Get a node tags: - - cps-data + - cps-data x-codegen-request-body-name: xpath /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: get: @@ -1126,42 +1126,51 @@ paths: anchor and dataspace operationId: getNodeByDataspaceAndAnchorV2 parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: "Number of descendants to query. Allowed values are 'none', 'all',\ + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: "Number of descendants to query. Allowed values are 'none', 'all',\ \ 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive\ \ number." - in: query - name: descendants - required: false - schema: - default: none - example: "3" - type: string + in: query + name: descendants + required: false + schema: + default: none + example: "3" + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string responses: "200": content: @@ -1172,6 +1181,15 @@ paths: value: null schema: type: object + application/xml: + examples: + dataSample: + $ref: '#/components/examples/dataSampleXml' + value: null + schema: + type: object + xml: + name: stores description: OK "400": content: @@ -1205,7 +1223,7 @@ paths: description: Internal Server Error summary: Get a node tags: - - cps-data + - cps-data x-codegen-request-body-name: xpath /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: delete: @@ -1213,49 +1231,49 @@ paths: xpath. operationId: deleteDataNode parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string responses: "204": content: {} @@ -1292,63 +1310,65 @@ paths: description: Internal Server Error summary: Delete a data node tags: - - cps-data + - cps-data patch: description: Update a data node leaves for a given dataspace and anchor and a parent node xpath. This operation is currently supported for one top level data node only. operationId: updateNodeLeaves parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string - - description: Content type in header - in: header - name: Content-Type - required: true - schema: - example: application/json - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string requestBody: content: application/json: @@ -1410,61 +1430,72 @@ paths: description: Internal Server Error summary: Update node leaves tags: - - cps-data + - cps-data post: description: Create a node for a given anchor and dataspace operationId: createNode parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string - - description: Content type in header - in: header - name: Content-Type - required: true - schema: - example: application/json - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is set to false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string requestBody: content: application/json: @@ -1534,62 +1565,64 @@ paths: description: Internal Server Error summary: Create a node tags: - - cps-data + - cps-data put: description: "Replace a node with descendants for a given dataspace, anchor\ \ and a parent node xpath" operationId: replaceNode parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string - - description: Content type in header - in: header - name: Content-Type - required: true - schema: - example: application/json - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string requestBody: content: application/json: @@ -1651,45 +1684,45 @@ paths: description: Internal Server Error summary: Replace a node with descendants tags: - - cps-data + - cps-data /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: delete: deprecated: true description: Delete one or all list element(s) for a given anchor and dataspace operationId: deleteListOrListElement parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: true - schema: - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: true + schema: + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string responses: "204": content: {} @@ -1726,61 +1759,63 @@ paths: description: Internal Server Error summary: Delete one or all list element(s) tags: - - cps-data + - cps-data /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: post: description: Add list element(s) to a list for a given anchor and dataspace operationId: addListElements parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: true - schema: - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string - - description: Content type in header - in: header - name: Content-Type - required: true - schema: - example: application/json - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: true + schema: + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string requestBody: content: application/json: @@ -1840,53 +1875,53 @@ paths: description: Internal Server Error summary: Add list element(s) tags: - - cps-data + - cps-data put: description: "Replace list content under a given parent, anchor and dataspace" operationId: replaceListContent parameters: - - description: apiVersion - in: path - name: apiVersion - required: true - schema: - default: v2 - enum: - - v1 - - v2 - type: string - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: true - schema: - type: string - - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string + - description: apiVersion + in: path + name: apiVersion + required: true + schema: + default: v2 + enum: + - v1 + - v2 + type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: true + schema: + type: string + - description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string requestBody: content: application/json: @@ -1939,55 +1974,55 @@ paths: description: Internal Server Error summary: Replace list content tags: - - cps-data + - cps-data /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta: get: description: Get delta between two anchors within a given dataspace operationId: getDeltaByDataspaceAndAnchors parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: source-anchor-name - in: path - name: source-anchor-name - required: true - schema: - example: my-anchor - type: string - - description: target-anchor-name - in: query - name: target-anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string - - description: "Number of descendants to query. Allowed values are 'none', 'all',\ + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: source-anchor-name + in: path + name: source-anchor-name + required: true + schema: + example: my-anchor + type: string + - description: target-anchor-name + in: query + name: target-anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string + - description: "Number of descendants to query. Allowed values are 'none', 'all',\ \ 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive\ \ number." - in: query - name: descendants - required: false - schema: - default: none - example: "3" - type: string + in: query + name: descendants + required: false + schema: + default: none + example: "3" + type: string responses: "200": content: @@ -2031,38 +2066,38 @@ paths: description: Internal Server Error summary: Get delta between anchors in the same dataspace tags: - - cps-data + - cps-data x-codegen-request-body-name: xpath post: description: Get delta between an anchor in a dataspace and JSON payload operationId: getDeltaByDataspaceAnchorAndPayload parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: source-anchor-name - in: path - name: source-anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" - examples: - container xpath: - value: /shops/bookstore - list attributes xpath: - value: "/shops/bookstore/categories[@code=1]" - in: query - name: xpath - required: false - schema: - default: / - type: string + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: source-anchor-name + in: path + name: source-anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + in: query + name: xpath + required: false + schema: + default: / + type: string requestBody: content: multipart/form-data: @@ -2121,47 +2156,47 @@ paths: description: Internal Server Error summary: Get delta between an anchor and JSON payload tags: - - cps-data + - cps-data /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: get: deprecated: true description: Query data nodes for the given dataspace and anchor using CPS path operationId: getNodesByDataspaceAndAnchorAndCpsPath parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" - examples: - container cps path: - value: //bookstore - list attributes cps path: - value: "//categories[@code=1]" - in: query - name: cps-path - required: false - schema: - default: / - type: string - - description: include-descendants - in: query - name: include-descendants - required: false - schema: - default: false - example: false - type: boolean + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: "//categories[@code=1]" + in: query + name: cps-path + required: false + schema: + default: / + type: string + - description: include-descendants + in: query + name: include-descendants + required: false + schema: + default: false + example: false + type: boolean responses: "200": content: @@ -2205,49 +2240,58 @@ paths: description: Internal Server Error summary: Query data nodes tags: - - cps-query + - cps-query x-codegen-request-body-name: xpath /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: get: description: Query data nodes for the given dataspace and anchor using CPS path operationId: getNodesByDataspaceAndAnchorAndCpsPathV2 parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: anchor-name - in: path - name: anchor-name - required: true - schema: - example: my-anchor - type: string - - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" - examples: - container cps path: - value: //bookstore - list attributes cps path: - value: "//categories[@code=1]" - in: query - name: cps-path - required: false - schema: - default: / - type: string - - description: "Number of descendants to query. Allowed values are 'none', 'all',\ + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: anchor-name + in: path + name: anchor-name + required: true + schema: + example: my-anchor + type: string + - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: "//categories[@code=1]" + in: query + name: cps-path + required: false + schema: + default: / + type: string + - description: "Number of descendants to query. Allowed values are 'none', 'all',\ \ 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive\ \ number." - in: query - name: descendants - required: false - schema: - default: none - example: "3" - type: string + in: query + name: descendants + required: false + schema: + default: none + example: "3" + type: string + - description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string responses: "200": content: @@ -2258,6 +2302,15 @@ paths: value: null schema: type: object + application/xml: + examples: + dataSample: + $ref: '#/components/examples/dataSampleXml' + value: null + schema: + type: object + xml: + name: stores description: OK "400": content: @@ -2291,7 +2344,7 @@ paths: description: Internal Server Error summary: Query data nodes tags: - - cps-query + - cps-query x-codegen-request-body-name: xpath /v2/dataspaces/{dataspace-name}/nodes/query: get: @@ -2299,51 +2352,51 @@ paths: path operationId: getNodesByDataspaceAndCpsPath parameters: - - description: dataspace-name - in: path - name: dataspace-name - required: true - schema: - example: my-dataspace - type: string - - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" - examples: - container cps path: - value: //bookstore - list attributes cps path: - value: "//categories[@code=1]" - in: query - name: cps-path - required: false - schema: - default: / - type: string - - description: "Number of descendants to query. Allowed values are 'none', 'all',\ + - description: dataspace-name + in: path + name: dataspace-name + required: true + schema: + example: my-dataspace + type: string + - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: "//categories[@code=1]" + in: query + name: cps-path + required: false + schema: + default: / + type: string + - description: "Number of descendants to query. Allowed values are 'none', 'all',\ \ 'direct', 1 (for direct), -1 (for all), 0 (for none) and any positive\ \ number." - in: query - name: descendants - required: false - schema: - default: none - example: "3" - type: string - - description: page index for pagination over anchors. It must be greater then - zero if provided. - in: query - name: pageIndex - required: false - schema: - example: 1 - type: integer - - description: number of records (anchors) per page. It must be greater then - zero if provided. - in: query - name: pageSize - required: false - schema: - example: 10 - type: integer + in: query + name: descendants + required: false + schema: + default: none + example: "3" + type: string + - description: page index for pagination over anchors. It must be greater then + zero if provided. + in: query + name: pageIndex + required: false + schema: + example: 1 + type: integer + - description: number of records (anchors) per page. It must be greater then + zero if provided. + in: query + name: pageSize + required: false + schema: + example: 10 + type: integer responses: "200": content: @@ -2387,7 +2440,7 @@ paths: description: Internal Server Error summary: Query data nodes across anchors tags: - - cps-query + - cps-query x-codegen-request-body-name: xpath components: examples: @@ -2396,52 +2449,52 @@ components: test:bookstore: bookstore-name: Chapters categories: - - code: 1 - name: SciFi - - code: 2 - name: kids + - code: 1 + name: SciFi + - code: 2 + name: kids dataSampleXml: value: <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <bookstore xmlns="org:onap:ccsdk:sample"> <bookstore-name>Chapters</bookstore-name> <categories> <code>1</code> <name>SciFi</name> <code>2</code> <name>kids</name> </categories> </bookstore> </stores> deltaReportSample: value: - - action: create - xpath: "/bookstore/categories/[@code=3]" - target-data: - code: "3," - name: kidz - - action: remove - xpath: "/bookstore/categories/[@code=1]" - source-data: - code: "1," - name: Fiction - - action: replace - xpath: "/bookstore/categories/[@code=2]" - source-data: - name: Funny - target-data: - name: Comic + - action: create + xpath: "/bookstore/categories/[@code=3]" + target-data: + code: "3," + name: kidz + - action: remove + xpath: "/bookstore/categories/[@code=1]" + source-data: + code: "1," + name: Fiction + - action: replace + xpath: "/bookstore/categories/[@code=2]" + source-data: + name: Funny + target-data: + name: Comic dataSampleAcrossAnchors: value: - - anchorName: bookstore1 - dataNode: - test:bookstore: - bookstore-name: Chapters - categories: - - code: 1 - name: SciFi - - code: 2 - name: kids - - anchorName: bookstore2 - dataNode: - test:bookstore: - bookstore-name: Chapters - categories: - - code: 1 - name: SciFi - - code: 2 - name: kids + - anchorName: bookstore1 + dataNode: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 1 + name: SciFi + - code: 2 + name: kids + - anchorName: bookstore2 + dataNode: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 1 + name: SciFi + - code: 2 + name: kids parameters: dataspaceNameInQuery: description: dataspace-name @@ -2459,8 +2512,8 @@ components: schema: default: v2 enum: - - v1 - - v2 + - v1 + - v2 type: string dataspaceNameInPath: description: dataspace-name @@ -2534,6 +2587,16 @@ components: default: none example: "3" type: string + contentTypeInHeader: + description: Content type in header + in: header + name: Content-Type + required: true + schema: + enum: + - application/json + - application/xml + type: string observedTimestampInQuery: description: observed-timestamp in: query @@ -2542,14 +2605,16 @@ components: schema: example: 2021-03-21T00:10:34.030-0100 type: string - contentTypeInHeader: - description: Content type in header - in: header - name: Content-Type - required: true + dryRunInQuery: + description: "Boolean flag to validate data, without persisting it. Default\ + \ value is set to false." + in: query + name: dry-run + required: false schema: - example: application/json - type: string + default: false + example: false + type: boolean requiredXpathInQuery: description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" examples: @@ -2725,19 +2790,19 @@ components: format: binary type: string required: - - file + - file type: object SchemaSetDetails: example: dataspaceName: my-dataspace name: my-schema-set moduleReferences: - - name: my-module-reference-name - namespace: my-module-reference-namespace - revision: my-module-reference-revision - - name: my-module-reference-name - namespace: my-module-reference-namespace - revision: my-module-reference-revision + - name: my-module-reference-name + namespace: my-module-reference-namespace + revision: my-module-reference-revision + - name: my-module-reference-name + namespace: my-module-reference-namespace + revision: my-module-reference-revision properties: dataspaceName: example: my-dataspace @@ -2750,7 +2815,7 @@ components: example: my-schema-set type: string required: - - moduleReferences + - moduleReferences title: Schema set details by dataspace and schemasetName type: object ModuleReferences: @@ -2777,16 +2842,16 @@ components: test:bookstore: bookstore-name: Chapters categories: - - code: 1 - name: SciFi - - code: 2 - name: kids + - code: 1 + name: SciFi + - code: 2 + name: kids type: object file: format: binary type: string required: - - json + - json type: object securitySchemes: basicAuth: diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index d710316fc3..ab83ed2ae6 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: description: NCMP Inventory API title: NCMP Inventory API - version: 3.5.2 + version: 3.5.4 servers: - url: /ncmpInventory security: @@ -86,8 +86,8 @@ paths: - network-cm-proxy-inventory /v1/ch/cmHandles: get: - description: Get all cm handle IDs for a registered DMI plugin - operationId: getAllCmHandleIdsForRegisteredDmi + description: Get all cm handle references for a registered DMI plugin + operationId: getAllCmHandleReferencesForRegisteredDmi parameters: - description: dmi-plugin-identifier in: query @@ -96,6 +96,14 @@ paths: schema: example: my-dmi-plugin type: string + - description: Boolean parameter to determine if returned value(s) will be cm + handle ids or alternate ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean responses: "200": content: @@ -125,15 +133,25 @@ paths: schema: $ref: '#/components/schemas/ErrorMessage' description: Internal Server Error - summary: "Get all cm handle IDs for a registered DMI plugin (DMI plugin, DMI\ - \ data plugin, DMI model plugin)" + summary: "Get all cm handle references for a registered DMI plugin (DMI plugin,\ + \ DMI data plugin, DMI model plugin)" tags: - network-cm-proxy-inventory /v1/ch/searches: post: - description: "Query and get CMHandleIds for additional properties, public properties\ - \ and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin)." + description: "Query and get cm handle references for additional properties,\ + \ public properties and registered DMI plugin (DMI plugin, DMI data plugin,\ + \ DMI model plugin)." operationId: searchCmHandleIds + parameters: + - description: Boolean parameter to determine if returned value(s) will be cm + handle ids or alternate ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean requestBody: content: application/json: @@ -182,6 +200,15 @@ components: schema: example: my-dmi-plugin type: string + outputAlternateIdOptionInQuery: + description: Boolean parameter to determine if returned value(s) will be cm + handle ids or alternate ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean responses: NoContent: content: {} diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index a227addda5..e7256c0836 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: description: NCMP to CPS Proxy API title: NCMP to CPS Proxy API - version: 3.5.2 + version: 3.5.4 servers: - url: /ncmp security: @@ -20,13 +20,14 @@ paths: schema: example: ncmp-datastore:running type: string - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but @@ -130,13 +131,14 @@ paths: schema: example: ncmp-datastore:running type: string - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but @@ -264,13 +266,14 @@ paths: schema: example: ncmp-datastore:running type: string - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but @@ -377,13 +380,14 @@ paths: schema: example: ncmp-datastore:running type: string - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but @@ -495,13 +499,14 @@ paths: schema: example: ncmp-datastore:running type: string - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but @@ -693,7 +698,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' description: Bad Gateway - summary: Execute a data operation for group of cm handle ids + summary: Execute a data operation for group of cm handle references tags: - network-cm-proxy /v1/ch/{cm-handle}/data/ds/{datastore-name}/query: @@ -824,13 +829,14 @@ paths: handle operationId: getModuleReferencesByCmHandle parameters: - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string responses: "200": @@ -880,13 +886,14 @@ paths: \ with options to filter on module name and revision" operationId: getModuleDefinitions parameters: - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string - description: Filter for a module name.This is an optional parameter in: query @@ -1017,13 +1024,14 @@ paths: description: Retrieve CM handle details and properties by cm handle id operationId: retrieveCmHandleDetailsById parameters: - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string responses: "200": @@ -1070,13 +1078,14 @@ paths: description: Get CM handle properties by cm handle id operationId: getCmHandlePublicPropertiesByCmHandleId parameters: - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string responses: "200": @@ -1120,17 +1129,26 @@ paths: - network-cm-proxy /v1/ch/id-searches: post: - description: Execute cm handle query search and return a list of cm handle ids. - Any number of conditions can be applied. To be included in the result a cm-handle - must fulfill ALL the conditions. An empty collection will be returned in the - case that the cm handle does not match a condition. For more on cm handle - query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm + description: Execute cm handle query search and return a list of cm handle references. + Any number of conditions can be applied. To be included in the result a cm + handle must fulfill ALL the conditions. An empty collection will be returned + in the case that the cm handle does not match a condition. For more on cm + handle query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible to query on any data related to the cm handle. For more on CPS Path please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. operationId: searchCmHandleIds + parameters: + - description: Boolean parameter to determine if returned value(s) will be cm + handle ids or alternate ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean requestBody: content: application/json: @@ -1210,13 +1228,14 @@ paths: description: Get CM handle state by cm handle id operationId: getCmHandleStateByCmHandleId parameters: - - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + - description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network\ + \ CM Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string responses: "200": @@ -1464,14 +1483,15 @@ components: schema: example: ncmp-datastore:running type: string - cmHandleInPath: - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" + cmHandleReferenceInPath: + description: "The identifier (cmHandle or alternate) for a network function,\ + \ network element, subnetwork or any other cm object by managed Network CM\ + \ Proxy" in: path name: cm-handle required: true schema: - example: my-cm-handle + example: my-cm-handle-reference type: string resourceIdentifierInQuery: description: The format of resource identifier depend on the associated DMI @@ -1558,6 +1578,15 @@ components: required: true schema: type: string + cmHandleInPath: + description: "The identifier for a network function, network element, subnetwork\ + \ or any other cm object by managed Network CM Proxy" + in: path + name: cm-handle + required: true + schema: + example: my-cm-handle + type: string cpsPathInQuery: description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" examples: @@ -1588,6 +1617,15 @@ components: schema: example: 2024-01-22 type: string + outputAlternateIdOptionInQuery: + description: Boolean parameter to determine if returned value(s) will be cm + handle ids or alternate ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean dataSyncEnabled: description: Is used to enable or disable the data synchronization flag in: query @@ -1751,8 +1789,8 @@ components: type: string targetIds: items: - description: "targeted cm handles, maximum of 50 supported. If this limit\ - \ is exceeded the request wil be refused." + description: "targeted cm handle references, maximum of 200 supported.\ + \ If this limit is exceeded the request will be refused." example: "[\"da310eecdb8d44c2acc0ddaae01174b1\",\"c748c58f8e0b438f9fd1f28370b17d47\"\ ]" type: string diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml index 6b73d98ed6..1248c0d08b 100644 --- a/docs/api/swagger/policy-executor/openapi.yaml +++ b/docs/api/swagger/policy-executor/openapi.yaml @@ -18,55 +18,170 @@ openapi: 3.0.3 info: + description: Allows NCMP to execute a policy defined by a third party implementation + before proceeding with a CM operation title: Policy Executor - description: "Allows NCMP to execute a policy defined by a third party implementation before proceeding with a CM operation" version: 1.0.0 +servers: +- url: / +security: +- bearerAuth: [] tags: - - name: policy-executor - description: "Execute all your policies" +- description: Execute all your policies + name: policy-executor paths: /policy-executor/api/v1/{action}: post: - description: "Fire a Policy action" + description: Fire a Policy action operationId: executePolicyAction parameters: - - $ref: '#/components/parameters/authorizationInHeader' - - $ref: '#/components/parameters/actionInPath' - requestBody: + - description: Bearer token may be used to identify client as part of a policy + explode: false + in: header + name: Authorization + required: false + schema: + type: string + style: simple + - description: "The policy action. Currently supported options: 'execute'" + explode: false + in: path + name: action required: true - description: "The action request body" + schema: + example: execute + type: string + style: simple + requestBody: content: application/json: schema: $ref: '#/components/schemas/PolicyExecutionRequest' - tags: - - policy-executor + description: The action request body + required: true responses: - '200': - description: "Successful policy execution" + "200": content: application/json: schema: $ref: '#/components/schemas/PolicyExecutionResponse' - '400': - $ref: '#/components/responses/BadRequest' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - '500': - $ref: '#/components/responses/InternalServerError' - + description: Successful policy execution + "400": + content: + application/json: + example: + status: 400 + message: Bad Request + details: The provided request is not valid + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Bad request + "401": + content: + application/json: + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Unauthorized request + "403": + content: + application/json: + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Request forbidden + "500": + content: + application/json: + example: + status: 500 + message: Internal Server Error + details: Internal server error occurred + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Internal server error + tags: + - policy-executor components: - securitySchemes: - bearerAuth: - type: http - description: "Bearer token (from client that called CPS-NCMP),used by policies to identify the client" - scheme: bearer + parameters: + actionInPath: + description: "The policy action. Currently supported options: 'execute'" + explode: false + in: path + name: action + required: true + schema: + example: execute + type: string + style: simple + authorizationInHeader: + description: Bearer token may be used to identify client as part of a policy + explode: false + in: header + name: Authorization + required: false + schema: + type: string + style: simple + responses: + BadRequest: + content: + application/json: + example: + status: 400 + message: Bad Request + details: The provided request is not valid + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Bad request + Unauthorized: + content: + application/json: + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Unauthorized request + Forbidden: + content: + application/json: + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Request forbidden + InternalServerError: + content: + application/json: + example: + status: 500 + message: Internal Server Error + details: Internal server error occurred + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Internal server error + NotImplemented: + content: + application/json: + example: + status: 501 + message: Not Implemented + details: Method not implemented + schema: + $ref: '#/components/schemas/ErrorMessage' + description: Method not (yet) implemented schemas: ErrorMessage: - type: object - title: Error properties: status: type: string @@ -74,125 +189,73 @@ components: type: string details: type: string - - Request: + title: Error type: object + Request: + example: + schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 + data: "{}" properties: schema: + description: The schema for the data in this request. The schema name should + include the type of operation + example: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 type: string - description: "The schema for the data in this request. The schema name should include the type of operation" - example: "org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0" data: + description: The data related to the request. The format of the object is + determined by the schema type: object - description: "The data related to the request. The format of the object is determined by the schema" required: - - schema - - data - - PolicyExecutionRequest: + - data + - schema type: object + PolicyExecutionRequest: + example: + decisionType: allow + requests: + - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 + data: "{}" + - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 + data: "{}" properties: decisionType: - type: string description: "The type of decision. Currently supported options: 'allow'" - example: "allow" + example: allow + type: string requests: - type: array items: $ref: '#/components/schemas/Request' + type: array required: - - decisionType - - requests - - PolicyExecutionResponse: + - decisionType + - requests type: object + PolicyExecutionResponse: + example: + decision: deny + decisionId: 550e8400-e29b-41d4-a716-446655440000 + message: Object locked due to recent change properties: decisionId: + description: Unique ID for the decision (for auditing purposes) + example: 550e8400-e29b-41d4-a716-446655440000 type: string - description: "Unique ID for the decision (for auditing purposes)" - example: "550e8400-e29b-41d4-a716-446655440000" decision: - type: string description: "The decision outcome. Currently supported values: 'allow','deny'" - example: "deny" + example: deny + type: string message: + description: Additional information regarding the decision outcome + example: Object locked due to recent change type: string - description: "Additional information regarding the decision outcome" - example: "Object locked due to recent change" required: - - decisionId - - decision - - message - - responses: - BadRequest: - description: "Bad request" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 400 - message: "Bad Request" - details: "The provided request is not valid" - Unauthorized: - description: "Unauthorized request" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 401 - message: "Unauthorized request" - details: "This request is unauthorized" - Forbidden: - description: "Request forbidden" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 403 - message: "Request Forbidden" - details: "This request is forbidden" - - InternalServerError: - description: "Internal server error" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 500 - message: "Internal Server Error" - details: "Internal server error occurred" - - NotImplemented: - description: "Method not (yet) implemented" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 501 - message: "Not Implemented" - details: "Method not implemented" - - parameters: - actionInPath: - name: action - in: path - description: "The policy action. Currently supported options: 'execute'" - required: true - schema: - type: string - example: "execute" - authorizationInHeader: - name: Authorization - in: header - description: "Bearer token may be used to identify client as part of a policy" - schema: - type: string - -security: - - bearerAuth: [] + - decision + - decisionId + - message + type: object + securitySchemes: + bearerAuth: + description: "Bearer token (from client that called CPS-NCMP),used by policies\ + \ to identify the client" + scheme: bearer + type: http diff --git a/docs/architecture.rst b/docs/architecture.rst index d21b213bb8..3cb0a26ce7 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -19,7 +19,7 @@ parameters that need to be used by ONAP. CPS is no longer a stand alone component and is released along with and the NCMP-DMI-Plugin. Project page describing eventual scope and ambition is here: -`Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_ +`Configuration Persistence Service Project <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16398157/Configuration+Persistence+Service+Project>`_ This page reflects the state for the latest release. diff --git a/docs/cm-notification-subscriptions.rst b/docs/cm-notification-subscriptions.rst index 14e871addc..e1d1c2f800 100644 --- a/docs/cm-notification-subscriptions.rst +++ b/docs/cm-notification-subscriptions.rst @@ -6,14 +6,14 @@ .. _cmNotificationSubscriptions: -CM Data Subscriptions -##################### +CM Data Subscriptions and Notifications +####################################### .. toctree:: :maxdepth: 1 -Introduction -============ +CM Data Subscriptions +===================== CM Subscriptions are created to subscribe to notifications for CM related changes that happened in the network based on predicates. Predicates can be used to filter on CM Handle (id), Datastore and Xpath. @@ -44,5 +44,17 @@ The response for the involved subscription participants for the Create and Delet **Note.** The Cm Subscription feature relies on the DMI Plugin support for applying the subscriptions. This support is currently not implemented in the ONAP DMI Plugin. +CM Data Notifications +===================== +CM Notifications are triggered by any change in the network, provided the client has already set up a CM Subscription to receive such notifications. Once the events are generated, they are processed by NCMP and forwarded to the client in the same format. + +**Note.** Currently, CM Notifications are sent regardless of the CM Subscriptions. Notifications controlled by CM Subscription have not yet been delivered. + +The CM Notification Event follows the structure outlined in the schema below: + +:download:`CM Data Notification Event Schema <schemas/dmidataavc/avc-event-schema-1.0.0.json>` + +**Note.** NCMP uses the CM Notification event key from the source topic to forward notifications to the client, ensuring that the order of notifications within a topic partition is maintained during forwarding. +**Note.** If the notification key from the source topic is null, NCMP cannot guarantee the order of events within a topic partition when forwarding. diff --git a/docs/cps-delta-endpoints.rst b/docs/cps-delta-endpoints.rst index ecb7550f44..47f52dc670 100644 --- a/docs/cps-delta-endpoints.rst +++ b/docs/cps-delta-endpoints.rst @@ -1,38 +1,53 @@ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. http://creativecommons.org/licenses/by/4.0 -.. Copyright (C) 2021 Pantheon.tech .. Copyright (C) 2024 TechMahindra Ltd. .. _cpsDeltaEndpoints: -.. toctree:: - :maxdepth: 1 - CPS Delta Endpoints ################### -The CPS Delta feature provides 1 endpoint: +The CPS Delta feature provides two endpoints: + +1. GET /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta +2. POST /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta -- /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/delta +Common Parameters of both the endpoints +--------------------------------------- +Common Path Parameters +^^^^^^^^^^^^^^^^^^^^^^ +The following parameters are common inputs between the two endpoints: + - **dataspace-name:** name of dataspace where the anchor(s) to be used for delta generation are stored. + - **source-anchor-name:** the source anchor name, the data under this anchor will be the reference data for delta report generation -Description ------------ -The following is a Get endpoint, which allows the user to find the delta between configurations stored under two anchors within the same dataspace. +Common Query Parameter +^^^^^^^^^^^^^^^^^^^^^^ +Both the endpoint need the following query parameters as input: + - **xpath:** the xpath to a particular data node. + - Example: /bookstore/categories[@code='1'] -Path Parameters ---------------- -The endpoint takes 2 path parameters as input: - - **dataspace-name:** name of dataspace where the 2 anchors to be used for delta generation are stored. - - **anchor-name:** the source anchor name, the data under this anchor will be the reference data for delta report generation +Description of API 1: Delta between 2 Anchors +--------------------------------------------- +This API performs a GET operation, which allows the user to find the delta between configurations stored under two anchors within the same dataspace. The API has following input parameters: -Query Parameters ----------------- -The endpoint takes 3 query parameters as input: - - **target-anchor-name:** the data retrieved from target anchor gets compared against the data retrieved from source anchor - - **xpath:** the xpath to a particular data node, Example: /bookstore/categories[@code='1'] +Specific Query Parameters +^^^^^^^^^^^^^^^^^^^^^^^^^ +The endpoint takes following additional/specific query parameters as input: + - **target-anchor-name:** the data retrieved from target anchor is compared against the data retrieved from source anchor - **descendants:** specifies the number of descendants to query. +Description of API 2: Delta between Anchor and JSON payload +----------------------------------------------------------- +This API performs a POST operation, which allows the user to find the delta between configurations stored under an anchors and a JSON payload provided as part of the request. The API has the following input parameters: + +Request Body for Endpoint 2 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The endpoint accepts a **multipart/form-data** input as part of request body. This allows the user to provide the following inputs as part of request body: + - **JSON payload:** this field accepts a valid JSON string as an input. The data provided as part of this JSON will be parsed using the schema, the schema is either retrieved using the anchor name or it can be provided as part of request body using the optional parameter of request body defined below. Once the JSON is parsed and validated, it is compared to the data fetched from the source anchor and the delta report is generated. + - **schema-context:** this is an optional parameter and allows the user to provide the schema of the JSON payload, as a yang or zip file, and this schema can be used to parse the JSON string in case the schema of JSON differs from the schema associated with source anchor. If the schema of JSON payload is similar to the schema associated with the anchor then this parameter can be left empty. + Sample Delta Report ------------------- +Both the APIs have the same format for the delta report. The format is as follows: .. code-block:: json diff --git a/docs/cps-delta-feature.rst b/docs/cps-delta-feature.rst index 34aa43df53..ee3df45378 100644 --- a/docs/cps-delta-feature.rst +++ b/docs/cps-delta-feature.rst @@ -1,6 +1,5 @@ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. http://creativecommons.org/licenses/by/4.0 -.. Copyright (C) 2021 Pantheon.tech .. Copyright (C) 2024 TechMahindra Ltd. .. _cpsDelta: @@ -10,24 +9,26 @@ CPS Delta Feature ################# -- The concept of CPS Delta Feature is to have the ability to find the delta or difference between two configurations stored in CPS DB. +- The concept of CPS Delta Feature is to have the ability to find the delta or difference between two configurations in CPS. -- The Delta feature brings a new functionality: +- The Delta feature provides two new endpoints, providing the following functionalities: - Ability to find the delta between the configurations stored in two anchors within the same dataspace. + - Ability to find the delta between the configurations stored under an anchor and a JSON payload provided as part of REST request. -The calculated differences can then be used to generate a Delta Report which can be sent over the Kafka Notification Service to the user. +The difference found are compiled into a Delta Report returned by the server. This report can be used in several different ways, one such use case is sending it over the Kafka Notification Service to the user. Delta Report Format ------------------- -The Delta Report is based on the RFC 9144, which defines a set of parameters to be included in the Delta Report. In CPS only the relevant parameters defined in RFC 9144 are used. These include: - - **action:** This parameter defines how the data nodes changed between two configurations. If data nodes are added, deleted or modified then the 'action' parameter in the delta report will be set to 'create', 'remove' or 'replace' respectively for each data node. +The Delta Report is based on the RFC 6902 and RFC 9144, which define a set of parameters to be included in the Delta Report. In CPS only the relevant parameters defined in the RFCs are used. These include: + - **action:** This parameter defines how the data nodes changed between two configurations. If data nodes are added, deleted or modified then the 'action' parameter in the delta report will be set to one of the following pre-defined operation values, i.e., 'create', 'remove' or 'replace' respectively for each data node. - **xpath:** This parameter will provide the xpath of each data node that has been either added, deleted or modified. - **source-data:** this parameter is added to the delta report when a data node is removed or updated, this represents the source/reference data against which the delta is being generated. In case of newly added data node this field is not included in the delta report. - **target-data:** this parameter is added to the delta report when a data node is added or updated, this represents the data values that are being compared to the source data. In case of a data node being removed this field is not included in the delta report. **Note.** In case of an existing data node being modified, both the source-data and target-data fields are present in the delta report. +**Additional Information.** `Analysis of RFC 9144 and RFC 6902 for Delta Report generation <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16523520/Analysis+of+RFC+9144+and+RFC6902+for+Delta+Report+generation>`_ Mechanism for Delta generation ------------------------------ diff --git a/docs/cps-scheduled-processes.rst b/docs/cps-scheduled-processes.rst index 032b4b143b..c204e6ca0a 100644 --- a/docs/cps-scheduled-processes.rst +++ b/docs/cps-scheduled-processes.rst @@ -22,9 +22,11 @@ Module Sync ----------- The module sync is a user :ref:`configurable timed process<additional-cps-ncmp-customizations>`, which is set to search for CM-Handles within CPS with an *'ADVISED'* state. -Once the CM-Handle(s) is processed by the module sync, the CM-Handle state is then set to *'READY'*, if the process completes successfully. +Once the CM-Handle is processed by the module sync, the CM-Handle state is then set to *'READY'*, if the process completes successfully. If for any reason the module sync fails, the CM-Handle state will then be set to *'LOCKED'*, and the reason for the lock will also be stored within CPS. +CM-Handles in the *'LOCKED'* state will be retried when the system has availability. CM-Handles in a *'LOCKED'* +state are processed by the retry mechanism, by setting CM-Handle state back to *'ADVISED'* so the next sync cycle will process those again. Data Sync --------- @@ -33,13 +35,3 @@ which is set to search for CM-Handles with a sync state of *'UNSYNCHRONIZED'*. Once the CM-Handle(s) with a sync state of *'UNSYNCHRONIZED'* is processed by the data sync, the CM-Handle sync state is then set to *'SYNCHRONIZED'*, if the process completes successfully. If the data sync fails, the CM-Handle sync state will remain as *'UNSYNCHRONIZED'*, and will be re-attempted. - -Retry Mechanism ---------------- -The retry mechanism is a user :ref:`configurable timed process<additional-cps-ncmp-customizations>`, -which is used to search for CM-Handles which are currently in a *'LOCKED'* state. -If the CM-Handle is ready to be retried then, the CM-Handle(s) in a *'LOCKED'* state is processed by the retry mechanism, -the CM-Handle state is then set to *'ADVISED'*. -Whether the CM-Handle is ready to be retried is dependent on both the number of attempts to sync the CM-Handle, -and the last update time of the CM-Handle state. -With each new attempt to unlock the CM-Handle, the time until the CM-Handle can next be retried is doubled. diff --git a/docs/deployment.rst b/docs/deployment.rst index ba8fcd9347..3f0c1eddc5 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -19,6 +19,24 @@ CPS uses PostgreSQL database. As per the `PostgreSQL documentation on resource c parameter should be set between 25% and 40% of total memory. It has a default value of 128 megabytes, so this should be set appropriately. For example, given a database with 2GB of memory, 512MB is a recommended value. +CPS and NCMP Configuration +========================== + +JVM Memory Allocation + +Allocating 75% of the container's memory to the JVM heap ensures efficient memory management. +This helps the JVM make the best use of the allocated resources while leaving enough memory for other processes. + +.. code-block:: yaml + + JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0" + +Load balancer configuration +=========================== + +For optimal performance in CPS/NCMP, load balancers should be configured to use a least-requests policy, also known as +least-connected. Use of round-robin load balancing can lead to instability. + CPS OOM Charts ============== The CPS kubernetes chart is located in the `OOM repository <https://github.com/onap/oom/tree/master/kubernetes/cps>`_. @@ -284,20 +302,15 @@ Additional CPS-NCMP Customizations | | | | | | See also :ref:`cps_common_credentials_retrieval`. | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.ncmp.timers | Specifies the delay in milliseconds in which the module sync watch dog will wake again after finishing. | ``30000`` | +| config.ncmp.timers | Specifies the delay in milliseconds in which the module sync watch dog will wake again after finishing. | ``5000`` | | .advised-modules-sync.sleep-time-ms | | | | | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.ncmp.timers | Specifies the delay in milliseconds in which the retry mechanism watch dog | | -| .locked-modules-sync.sleep-time-ms | will wake again after finishing. | ``300000`` | -| | | | -| | | | -+-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | config.ncmp.timers | Specifies the delay in milliseconds in which the data sync watch dog will wake again after finishing. | ``30000`` | | .cm-handle-data-sync.sleep-time-ms | | | | | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.additional.ncmp.dmi.httpclient | Specifies the maximum time in seconds, to wait for establishing a connection for the HTTP Client. | ``180`` | +| config.additional.ncmp.dmi.httpclient | Specifies the maximum time in seconds, to wait for establishing a connection for the HTTP Client. | ``30`` | | .connectionTimeoutInSeconds | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | config.additional.ncmp.dmi.httpclient | Specifies the maximum number of connections allowed per route in the HTTP client. | ``50`` | @@ -319,32 +332,27 @@ The latest instructions are covered in the `README <https://github.com/onap/cps/ .. Below Label is used by documentation for other CPS components to link here, do not remove even if it gives a warning .. _cps_common_distributed_datastructures: -CPS-Core and NCMP Distributed Datastructures -============================================ +NCMP Distributed Data Structures +================================ -CPS-Core and NCMP both internally uses embedded distributed datastructure to replicate the state across various instances for low latency. -These instances require some additional ports to be available. The default range starts from 5701 and based on the number of instances configured they are incremented sequentially. +NCMP utilizes embedded distributed data structures to replicate state across various instances, ensuring low latency and high performance. Each JVM runs a Hazelcast instance to manage these data structures. To facilitate member visibility and cluster formation, an additional port (defaulting to 5701) must be available. Below are the list of distributed datastructures that we have. +--------------+------------------------------------+-----------------------------------------------------------+ -| Component | Datastructure name | Use | +| Component | Data Structure Name | Use | +==============+====================================+===========================================================+ -| cps-core | anchorDataCache | Used to resolve prefix for the container name. | -+--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | moduleSyncStartedOnCmHandles | Watchdog process to register cm handles. | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | dataSyncSemaphores | Watchdog process to sync data from the nodes. | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | moduleSyncWorkQueue | Queue used internally for workers to pick the task. | +--------------+------------------------------------+-----------------------------------------------------------+ -| cps-ncmp | untrustworthyCmHandlesSet | Stores untrustworthy cm handles whose trust level is NONE.| +| cps-ncmp | trustLevelPerCmHandle | Stores the trust level per cm handle id | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | trustLevelPerDmiPlugin | Stores the trust level for the dmi-plugins. | +--------------+------------------------------------+-----------------------------------------------------------+ -| cps-ncmp | moduleSetTagCacheMapConfig | Stores the module set tags for cm handles. | -+--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | cmNotificationSubscriptionCache | Stores and tracks cm notification subscription requests. | +--------------+------------------------------------+-----------------------------------------------------------+ -Total number of caches : 8 +Total number of caches : 6 diff --git a/docs/ncmp-cmhandle-querying.rst b/docs/ncmp-cmhandle-querying.rst index 529297daa7..2e534d87ff 100644 --- a/docs/ncmp-cmhandle-querying.rst +++ b/docs/ncmp-cmhandle-querying.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) 2022-2023 Nordix Foundation +.. Copyright (C) 2022-2024 Nordix Foundation .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING .. _cmhandlequerying: @@ -19,9 +19,20 @@ For querying CM Handles we have two Post endpoints: - ncmp/v1/ch/searches Returns all CM Handles which match the query properties provided. Gives a JSON payload of the **details** of all matching CM Handles. -- ncmp/v1/ch/id-searches Returns all CM Handles IDs which match the query properties provided. Gives a JSON payload of the **ids** of all matching CM Handles. +- ncmp/v1/ch/id-searches Returns all CM Handles IDs or Alternate IDs which match the query properties provided. Gives a JSON payload of the **ids** of all matching CM Handles. -/searches returns whole CM Handle object (data) whereas /id-searches returns only CM Handle IDs. Otherwise these endpoints are intended to be functionally identical so both can be queried with the same request body. If no matching CM Handles are found an empty array is returned. +/searches returns whole CM Handle object (data) whereas /id-searches returns only CM Handle IDs or Alternate IDs. Otherwise these endpoints are intended to be functionally identical so both can be queried with the same request body. If no matching CM Handles are found an empty array is returned. + +Parameters +========== + +/id-searches can return either CM Handle IDs or Alternate IDs. This is controlled with an optional parameter outputAlternateId. + +- *outputAlternateId=true* returns Alternate IDs + +- *outputAlternateId=false* returns CM Handle IDs + +Note: Null values will default to false so /id-searches & /id-searches?outputAlternateId will both return CM Handle IDs Request Body ============ diff --git a/docs/ncmp-data-operation.rst b/docs/ncmp-data-operation.rst index 94d5ee9c0a..3352e03cf0 100644 --- a/docs/ncmp-data-operation.rst +++ b/docs/ncmp-data-operation.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) 2023 Nordix Foundation +.. Copyright (C) 2023-2024 Nordix Foundation .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING .. _cmHandleDataOperation: @@ -43,7 +43,7 @@ This endpoint executes data operation for given array of operations: | | | implementation. For ONAP DMI Plugin it will be RESTConf paths but it can| | | | really be anything. | +--------------------------+-------------+-------------------------------------------------------------------------+ - | targetIds | Yes | List of cm handle ids. | + | targetIds | Yes | List of cm handle references | +--------------------------+-------------+-------------------------------------------------------------------------+ The status codes used in the events resulting from these operations are defined here: diff --git a/docs/overview.rst b/docs/overview.rst index 11ed519b64..19ab8b4847 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -51,6 +51,6 @@ This is built previously from the CPS-NF-Proxy component. CPS Project =========== -* Wiki: `Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_ +* Wiki: `Configuration Persistence Service Project <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16398157/Configuration+Persistence+Service+Project>`_ * Contact Information: onap-discuss@lists.onap.org -* Meeting details: `Join <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_ & `Agenda <https://wiki.onap.org/pages/viewpage.action?pageId=111117075>`_ +* Meeting details: `Join <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_ & `Agenda <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/18644995>`_ diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 9b23fbc16b..c5b00d6888 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -16,6 +16,64 @@ CPS Release Notes .. * * * OSLO * * * .. ==================== +Version: 3.5.5 +============== + +Release Data +------------ + ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.5.5 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.5.5 Oslo | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | Not yet released | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Bug Fixes +--------- +3.5.5 + +Features +-------- +3.5.5 + +Version: 3.5.4 +============== + +Release Data +------------ + ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.5.4 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.5.4 Oslo | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | 2024 October 17 | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Bug Fixes +--------- +3.5.4 + - `CPS-2403 <https://lf-onap.atlassian.net/browse/CPS-2403>`_ Improve lock handling and queue management during CM-handle Module Sync. + +Features +-------- +3.5.4 + - `CPS-2408 <https://lf-onap.atlassian.net/browse/CPS-2408>`_ One Hazelcast instance per JVM to manage the distributed data structures. + Version: 3.5.3 ============== @@ -32,17 +90,25 @@ Release Data | **Release designation** | 3.5.3 Oslo | | | | +--------------------------------------+--------------------------------------------------------+ -| **Release date** | Not yet released | +| **Release date** | 2024 October 04 | | | | +--------------------------------------+--------------------------------------------------------+ Bug Fixes --------- 3.5.3 + - `CPS-2353 <https://lf-onap.atlassian.net/browse/CPS-2353>`_ Slow cmHandle registration when we use moduleSetTag, alternateId and dataProducerIdentifier + - `CPS-2395 <https://lf-onap.atlassian.net/browse/CPS-2395>`_ Retry mechanism (with back off algorithm) is removed with more frequent watchdog poll + - `CPS-2409 <https://lf-onap.atlassian.net/browse/CPS-2409>`_ Return NONE for get effective trust level api if the trust level caches empty (restart case) + - `CPS-2430 <https://lf-onap.atlassian.net/browse/CPS-2430>`_ Fix memory leak related to using arrays in Hibernate + Features -------- 3.5.3 + - `CPS-2247 <https://lf-onap.atlassian.net/browse/CPS-2247>`_ Policy Executor: Invoke Policy Executor and handle 'deny' response + - `CPS-2412 <https://lf-onap.atlassian.net/browse/CPS-2412>`_ Policy Executor: handle errors + - `CPS-2417 <https://lf-onap.atlassian.net/browse/CPS-2417>`_ Remove Hazelcast cache for prefix resolver Version: 3.5.2 @@ -68,17 +134,17 @@ Release Data Bug Fixes --------- 3.5.2 - - `CPS-2306 <https://jira.onap.org/browse/CPS-2306>`_ Update response message for data validation failure and make it consistent across APIs - - `CPS-2319 <https://jira.onap.org/browse/CPS-2319>`_ Fix "Create a node" and "Add List Elements" APIs response code - - `CPS-2372 <https://jira.onap.org/browse/CPS-2372>`_ Blank alternate ID overwrites existing one + - `CPS-2306 <https://lf-onap.atlassian.net/browse/CPS-2306>`_ Update response message for data validation failure and make it consistent across APIs + - `CPS-2319 <https://lf-onap.atlassian.net/browse/CPS-2319>`_ Fix "Create a node" and "Add List Elements" APIs response code + - `CPS-2372 <https://lf-onap.atlassian.net/browse/CPS-2372>`_ Blank alternate ID overwrites existing one Features -------- 3.5.2 - - `CPS-1812 <https://jira.onap.org/browse/CPS-1812>`_ CM Data Subscriptions ( Create, Delete and Merging ) with positive scenarios - - `CPS-2326 <https://jira.onap.org/browse/CPS-2326>`_ Uplift liquibase-core dependency to 4.28.0 - - `CPS-2353 <https://jira.onap.org/browse/CPS-2353>`_ Improve registration performance with moduleSetTag - - `CPS-2366 <https://jira.onap.org/browse/CPS-2366>`_ Improve registration performance with use of alternateID + - `CPS-1812 <https://lf-onap.atlassian.net/browse/CPS-1812>`_ CM Data Subscriptions ( Create, Delete and Merging ) with positive scenarios + - `CPS-2326 <https://lf-onap.atlassian.net/browse/CPS-2326>`_ Uplift liquibase-core dependency to 4.28.0 + - `CPS-2353 <https://lf-onap.atlassian.net/browse/CPS-2353>`_ Improve registration performance with moduleSetTag + - `CPS-2366 <https://lf-onap.atlassian.net/browse/CPS-2366>`_ Improve registration performance with use of alternateID Version: 3.5.1 ============== @@ -103,13 +169,13 @@ Release Data Bug Fixes --------- 3.5.1 - - `CPS-2302 <https://jira.onap.org/browse/CPS-2302>`_ Fix handling of special characters in moduleSetTag. + - `CPS-2302 <https://lf-onap.atlassian.net/browse/CPS-2302>`_ Fix handling of special characters in moduleSetTag. Features -------- 3.5.1 - - `CPS-2121 <https://jira.onap.org/browse/CPS-2121>`_ Enabled http client prometheus metrics and manage high cardinality using URL template. - - `CPS-2289 <https://jira.onap.org/browse/CPS-2289>`_ Support for CPS Path Query in NCMP Inventory Cm Handle Search. + - `CPS-2121 <https://lf-onap.atlassian.net/browse/CPS-2121>`_ Enabled http client prometheus metrics and manage high cardinality using URL template. + - `CPS-2289 <https://lf-onap.atlassian.net/browse/CPS-2289>`_ Support for CPS Path Query in NCMP Inventory Cm Handle Search. Version: 3.5.0 ============== @@ -138,8 +204,8 @@ Bug Fixes Features -------- 3.5.0 - - `CPS-989 <https://jira.onap.org/browse/CPS-989>`_ Replace RestTemplate with WebClient. - - `CPS-2172 <https://jira.onap.org/browse/CPS-2172>`_ Support for OpenTelemetry Tracing. + - `CPS-989 <https://lf-onap.atlassian.net/browse/CPS-989>`_ Replace RestTemplate with WebClient. + - `CPS-2172 <https://lf-onap.atlassian.net/browse/CPS-2172>`_ Support for OpenTelemetry Tracing. .. ========================= .. * * * NEW DELHI * * * @@ -168,12 +234,12 @@ Release Data Bug Fixes --------- 3.4.9 - - `CPS-2211 <https://jira.onap.org/browse/CPS-2211>`_ Toggle switch to disable CPS Core change events if not used by application. Set CPS_CHANGE_EVENT_NOTIFICATIONS_ENABLED environment variable for the same. + - `CPS-2211 <https://lf-onap.atlassian.net/browse/CPS-2211>`_ Toggle switch to disable CPS Core change events if not used by application. Set CPS_CHANGE_EVENT_NOTIFICATIONS_ENABLED environment variable for the same. Features -------- 3.4.9 - - `CPS-1836 <https://jira.onap.org/browse/CPS-1836>`_ Delta between anchor and JSON payload. + - `CPS-1836 <https://lf-onap.atlassian.net/browse/CPS-1836>`_ Delta between anchor and JSON payload. Version: 3.4.8 ============== @@ -198,10 +264,10 @@ Release Data Bug Fixes --------- 3.4.8 - - `CPS-2186 <https://jira.onap.org/browse/CPS-2186>`_ Report async task failures to client topic during data operations request - - `CPS-2190 <https://jira.onap.org/browse/CPS-2190>`_ Improve performance of NCMP module searches - - `CPS-2194 <https://jira.onap.org/browse/CPS-2194>`_ Added defaults for CPS and DMI username and password - - `CPS-2204 <https://jira.onap.org/browse/CPS-2204>`_ Added error handling for yang module upgrade operation + - `CPS-2186 <https://lf-onap.atlassian.net/browse/CPS-2186>`_ Report async task failures to client topic during data operations request + - `CPS-2190 <https://lf-onap.atlassian.net/browse/CPS-2190>`_ Improve performance of NCMP module searches + - `CPS-2194 <https://lf-onap.atlassian.net/browse/CPS-2194>`_ Added defaults for CPS and DMI username and password + - `CPS-2204 <https://lf-onap.atlassian.net/browse/CPS-2204>`_ Added error handling for yang module upgrade operation Features -------- @@ -229,13 +295,13 @@ Release Data Bug Fixes --------- 3.4.7 - - `CPS-2150 <https://jira.onap.org/browse/CPS-2150>`_ Fix for Async task execution failed by TimeoutException. + - `CPS-2150 <https://lf-onap.atlassian.net/browse/CPS-2150>`_ Fix for Async task execution failed by TimeoutException. Features -------- 3.4.7 - - `CPS-2061 <https://jira.onap.org/browse/CPS-2061>`_ Liquibase Steps Condensing and Cleanup. - - `CPS-2101 <https://jira.onap.org/browse/CPS-2101>`_ Uplift Spring Boot to 3.2.4 version. + - `CPS-2061 <https://lf-onap.atlassian.net/browse/CPS-2061>`_ Liquibase Steps Condensing and Cleanup. + - `CPS-2101 <https://lf-onap.atlassian.net/browse/CPS-2101>`_ Uplift Spring Boot to 3.2.4 version. Version: 3.4.6 ============== @@ -260,12 +326,12 @@ Release Data Bug Fixes --------- 3.4.6 - - `CPS-2126 <https://jira.onap.org/browse/CPS-2126>`_ Passing HTTP Authorization Bearer Token to DMI Plugins. + - `CPS-2126 <https://lf-onap.atlassian.net/browse/CPS-2126>`_ Passing HTTP Authorization Bearer Token to DMI Plugins. Features -------- - - `CPS-2133 <https://jira.onap.org/browse/CPS-2133>`_ Revert Uplift of Spring Boot version from 3.2.2 to 3.1.2 + - `CPS-2133 <https://lf-onap.atlassian.net/browse/CPS-2133>`_ Revert Uplift of Spring Boot version from 3.2.2 to 3.1.2 Notes ----- @@ -302,7 +368,7 @@ Bug Fixes Features -------- - - `CPS-2101 <https://jira.onap.org/browse/CPS-2101>`_ Uplift Spring Boot version to 3.2.2 + - `CPS-2101 <https://lf-onap.atlassian.net/browse/CPS-2101>`_ Uplift Spring Boot version to 3.2.2 Version: 3.4.4 @@ -328,12 +394,12 @@ Release Data Bug Fixes --------- 3.4.4 - - `CPS-2027 <https://jira.onap.org/browse/CPS-2027>`_ Upgrade Yang modules using module set tag functionalities fix + - `CPS-2027 <https://lf-onap.atlassian.net/browse/CPS-2027>`_ Upgrade Yang modules using module set tag functionalities fix Features -------- - - `CPS-2057 <https://jira.onap.org/browse/CPS-2057>`_ Leaf lists are sorted by default if Yang model does not specify order. - - `CPS-2087 <https://jira.onap.org/browse/CPS-2087>`_ Performance improvement of CPS Path Queries. + - `CPS-2057 <https://lf-onap.atlassian.net/browse/CPS-2057>`_ Leaf lists are sorted by default if Yang model does not specify order. + - `CPS-2087 <https://lf-onap.atlassian.net/browse/CPS-2087>`_ Performance improvement of CPS Path Queries. Version: 3.4.3 @@ -359,20 +425,20 @@ Release Data Bug Fixes --------- 3.4.3 - - `CPS-2000 <https://jira.onap.org/browse/CPS-2000>`_ Fix for Schema object cache not being distributed. - - `CPS-2027 <https://jira.onap.org/browse/CPS-2027>`_ Fixes for upgrade yang modules using module set tag. - - `CPS-2070 <https://jira.onap.org/browse/CPS-2070>`_ Add retry interval for Kafka consumer. + - `CPS-2000 <https://lf-onap.atlassian.net/browse/CPS-2000>`_ Fix for Schema object cache not being distributed. + - `CPS-2027 <https://lf-onap.atlassian.net/browse/CPS-2027>`_ Fixes for upgrade yang modules using module set tag. + - `CPS-2070 <https://lf-onap.atlassian.net/browse/CPS-2070>`_ Add retry interval for Kafka consumer. Features -------- - - `CPS-1824 <https://jira.onap.org/browse/CPS-1824>`_ CPS Delta between 2 anchors. - - `CPS-2072 <https://jira.onap.org/browse/CPS-2072>`_ Add maven classifier to Spring Boot JAR. - - `CPS-1135 <https://jira.onap.org/browse/CPS-1135>`_ Extend CPS Module API to allow retrieval single module definition. + - `CPS-1824 <https://lf-onap.atlassian.net/browse/CPS-1824>`_ CPS Delta between 2 anchors. + - `CPS-2072 <https://lf-onap.atlassian.net/browse/CPS-2072>`_ Add maven classifier to Spring Boot JAR. + - `CPS-1135 <https://lf-onap.atlassian.net/browse/CPS-1135>`_ Extend CPS Module API to allow retrieval single module definition. Notes ----- The maven build of cps-application has been changed so that the JAR produced by spring-boot-maven-plugin has a -*-springboot* classifier (`CPS-2072 <https://jira.onap.org/browse/CPS-2072>`_). This means that the filename +*-springboot* classifier (`CPS-2072 <https://lf-onap.atlassian.net/browse/CPS-2072>`_). This means that the filename of the Spring Boot JAR is *cps-application-3.4.3-springboot.jar*. Version: 3.4.2 @@ -402,10 +468,10 @@ Bug Fixes Features -------- - - `CPS-1638 <https://jira.onap.org/browse/CPS-1638>`_ Introduce trust level for CM handle. - - `CPS-1795 <https://jira.onap.org/browse/CPS-1795>`_ Double performance of CPS write operations (via write batching) - - `CPS-2018 <https://jira.onap.org/browse/CPS-2018>`_ Improve performance of CPS update operations. - - `CPS-2019 <https://jira.onap.org/browse/CPS-2019>`_ Improve performance of saving CM handles. + - `CPS-1638 <https://lf-onap.atlassian.net/browse/CPS-1638>`_ Introduce trust level for CM handle. + - `CPS-1795 <https://lf-onap.atlassian.net/browse/CPS-1795>`_ Double performance of CPS write operations (via write batching) + - `CPS-2018 <https://lf-onap.atlassian.net/browse/CPS-2018>`_ Improve performance of CPS update operations. + - `CPS-2019 <https://lf-onap.atlassian.net/browse/CPS-2019>`_ Improve performance of saving CM handles. Notes ----- @@ -417,7 +483,7 @@ Known Limitations, Issues and Workarounds *System Limitations* For upgrading, CPS uses Liquibase for database upgrades. In order to enable Hibernate write batching -(`CPS-1795 <https://jira.onap.org/browse/CPS-1795>`_), a change to the database entity ID generation is required. +(`CPS-1795 <https://lf-onap.atlassian.net/browse/CPS-1795>`_), a change to the database entity ID generation is required. As such, *this release does not fully support In-Service Software Upgrade* - CPS will not store new DataNodes and NCMP will not register new CM-handles during an upgrade with old and new versions of CPS running concurrently. Other operations (read, update, delete) are not impacted. @@ -446,19 +512,19 @@ Release Data Bug Fixes --------- 3.4.1 - - `CPS-1979 <https://jira.onap.org/browse/CPS-1979>`_ Bug fix for Invalid topic name suffix. + - `CPS-1979 <https://lf-onap.atlassian.net/browse/CPS-1979>`_ Bug fix for Invalid topic name suffix. Features -------- - CPS-Temporal is no longer supported and any related documentation has been removed. - - `CPS-1733 <https://jira.onap.org/browse/CPS-1733>`_ Upgrade YANG schema-set for CM handle without removing and adding it. - - `CPS-1980 <https://jira.onap.org/browse/CPS-1980>`_ Exposing health and cluster metrics for hazelcast. - - `CPS-1994 <https://jira.onap.org/browse/CPS-1994>`_ Use Apache Http Client for DMI REST requests. - - `CPS-2005 <https://jira.onap.org/browse/CPS-2005>`_ Removing notification feature for cps updated events ( exclusively used by cps-temporal ) + - `CPS-1733 <https://lf-onap.atlassian.net/browse/CPS-1733>`_ Upgrade YANG schema-set for CM handle without removing and adding it. + - `CPS-1980 <https://lf-onap.atlassian.net/browse/CPS-1980>`_ Exposing health and cluster metrics for hazelcast. + - `CPS-1994 <https://lf-onap.atlassian.net/browse/CPS-1994>`_ Use Apache Http Client for DMI REST requests. + - `CPS-2005 <https://lf-onap.atlassian.net/browse/CPS-2005>`_ Removing notification feature for cps updated events ( exclusively used by cps-temporal ) Known Issues ------------ - - `CPS-2000 <https://jira.onap.org/browse/CPS-2000>`_ Schema object cache is not distributed. + - `CPS-2000 <https://lf-onap.atlassian.net/browse/CPS-2000>`_ Schema object cache is not distributed. Version: 3.4.0 @@ -484,7 +550,7 @@ Release Data Bug Fixes --------- 3.4.0 - - `CPS-1956 <https://jira.onap.org/browse/CPS-1956>`_ Bug fix for No yang resources stored during cmhandle discovery. + - `CPS-1956 <https://lf-onap.atlassian.net/browse/CPS-1956>`_ Bug fix for No yang resources stored during cmhandle discovery. .. ======================== .. * * * MONTREAL * * * @@ -513,8 +579,8 @@ Release Data Bug Fixes --------- 3.3.9 - - `CPS-1923 <https://jira.onap.org/browse/CPS-1923>`_ CPS and NCMP changed management endpoint and port from /manage to /actuator and port same as cps application port. - - `CPS-1933 <https://jira.onap.org/browse/CPS-1933>`_ Setting up the class loader explicitly in hazelcast config. + - `CPS-1923 <https://lf-onap.atlassian.net/browse/CPS-1923>`_ CPS and NCMP changed management endpoint and port from /manage to /actuator and port same as cps application port. + - `CPS-1933 <https://lf-onap.atlassian.net/browse/CPS-1933>`_ Setting up the class loader explicitly in hazelcast config. Features -------- @@ -545,7 +611,7 @@ Bug Fixes Features -------- - - `CPS-1888 <https://jira.onap.org/browse/CPS-1888>`_ Uplift Spring Boot to 3.1.2. + - `CPS-1888 <https://lf-onap.atlassian.net/browse/CPS-1888>`_ Uplift Spring Boot to 3.1.2. Version: 3.3.7 ============== @@ -570,11 +636,11 @@ Release Data Bug Fixes --------- 3.3.7 - - `CPS-1866 <https://jira.onap.org/browse/CPS-1866>`_ Fix ClassDefNotFoundError in opendaylight Yang parser + - `CPS-1866 <https://lf-onap.atlassian.net/browse/CPS-1866>`_ Fix ClassDefNotFoundError in opendaylight Yang parser Features -------- - - `CPS-1789 <https://jira.onap.org/browse/CPS-1789>`_ CPS Upgrade to Springboot 3.0. + - `CPS-1789 <https://lf-onap.atlassian.net/browse/CPS-1789>`_ CPS Upgrade to Springboot 3.0. Note ---- @@ -603,13 +669,13 @@ Release Data Bug Fixes --------- 3.3.6 - - `CPS-1841 <https://jira.onap.org/browse/CPS-1841>`_ Update of top-level data node fails with exception - - `CPS-1842 <https://jira.onap.org/browse/CPS-1842>`_ Replace event-id with correlation-id for data read operation cloud event + - `CPS-1841 <https://lf-onap.atlassian.net/browse/CPS-1841>`_ Update of top-level data node fails with exception + - `CPS-1842 <https://lf-onap.atlassian.net/browse/CPS-1842>`_ Replace event-id with correlation-id for data read operation cloud event Features -------- - - `CPS-1696 <https://jira.onap.org/browse/CPS-1696>`_ Get Data Node to return entire List data node. - - `CPS-1819 <https://jira.onap.org/browse/CPS-1819>`_ Ability to disable sending authorization header. + - `CPS-1696 <https://lf-onap.atlassian.net/browse/CPS-1696>`_ Get Data Node to return entire List data node. + - `CPS-1819 <https://lf-onap.atlassian.net/browse/CPS-1819>`_ Ability to disable sending authorization header. Version: 3.3.5 @@ -638,7 +704,7 @@ Bug Fixes Features -------- - - `CPS-1760 <https://jira.onap.org/browse/CPS-1760>`_ Improve handling of special characters in Cps Paths + - `CPS-1760 <https://lf-onap.atlassian.net/browse/CPS-1760>`_ Improve handling of special characters in Cps Paths Version: 3.3.4 ============== @@ -666,7 +732,7 @@ Bug Fixes Features -------- - - `CPS-1767 <https://jira.onap.org/browse/CPS-1767>`_ Upgrade CPS to java 17 + - `CPS-1767 <https://lf-onap.atlassian.net/browse/CPS-1767>`_ Upgrade CPS to java 17 Version: 3.3.3 ============== @@ -694,9 +760,9 @@ Bug Fixes Features -------- - - `CPS-1515 <https://jira.onap.org/browse/CPS-1515>`_ Support Multiple CM-Handles for NCMP Get Operation - - `CPS-1675 <https://jira.onap.org/browse/CPS-1675>`_ Persistence write performance improvement(s) - - `CPS-1745 <https://jira.onap.org/browse/CPS-1745>`_ Upgrade to Openapi 3.0.3 + - `CPS-1515 <https://lf-onap.atlassian.net/browse/CPS-1515>`_ Support Multiple CM-Handles for NCMP Get Operation + - `CPS-1675 <https://lf-onap.atlassian.net/browse/CPS-1675>`_ Persistence write performance improvement(s) + - `CPS-1745 <https://lf-onap.atlassian.net/browse/CPS-1745>`_ Upgrade to Openapi 3.0.3 Version: 3.3.2 ============== @@ -721,15 +787,15 @@ Release Data Bug Fixes --------- 3.3.2 - - `CPS-1716 <https://jira.onap.org/browse/CPS-1716>`_ NCMP: Java Heap OutOfMemory errors and slow registration in case of 20k cmhandles + - `CPS-1716 <https://lf-onap.atlassian.net/browse/CPS-1716>`_ NCMP: Java Heap OutOfMemory errors and slow registration in case of 20k cmhandles Features -------- - - `CPS-1006 <https://jira.onap.org/browse/CPS-1006>`_ Extend CPS PATCH API to allow update of leaves for multiple data nodes - - `CPS-1273 <https://jira.onap.org/browse/CPS-1273>`_ Add <,> operators support to cps-path - - `CPS-1664 <https://jira.onap.org/browse/CPS-1664>`_ Use recursive SQL to fetch descendants in CpsPath queries to improve query performance - - `CPS-1676 <https://jira.onap.org/browse/CPS-1676>`_ Entity ID types do not match types in database definition - - `CPS-1677 <https://jira.onap.org/browse/CPS-1677>`_ Remove dataspace_id column from Fragment table + - `CPS-1006 <https://lf-onap.atlassian.net/browse/CPS-1006>`_ Extend CPS PATCH API to allow update of leaves for multiple data nodes + - `CPS-1273 <https://lf-onap.atlassian.net/browse/CPS-1273>`_ Add <,> operators support to cps-path + - `CPS-1664 <https://lf-onap.atlassian.net/browse/CPS-1664>`_ Use recursive SQL to fetch descendants in CpsPath queries to improve query performance + - `CPS-1676 <https://lf-onap.atlassian.net/browse/CPS-1676>`_ Entity ID types do not match types in database definition + - `CPS-1677 <https://lf-onap.atlassian.net/browse/CPS-1677>`_ Remove dataspace_id column from Fragment table Version: 3.3.1 ============== @@ -758,11 +824,11 @@ Bug Fixes Features -------- - - `CPS-1272 <https://jira.onap.org/browse/CPS-1272>`_ Add Contains operation to CPS Path - - `CPS-1573 <https://jira.onap.org/browse/CPS-1573>`_ Remove 32K limit for DB operations - - `CPS-1627 <https://jira.onap.org/browse/CPS-1627>`_ Dependency versions uplift because of vulnerability issues - - `CPS-1629 <https://jira.onap.org/browse/CPS-1629>`_ Ordering of leaf elements to support combination of AND/OR in cps-path - - `CPS-1637 <https://jira.onap.org/browse/CPS-1637>`_ Extend hazelcast to work on kubernetes + - `CPS-1272 <https://lf-onap.atlassian.net/browse/CPS-1272>`_ Add Contains operation to CPS Path + - `CPS-1573 <https://lf-onap.atlassian.net/browse/CPS-1573>`_ Remove 32K limit for DB operations + - `CPS-1627 <https://lf-onap.atlassian.net/browse/CPS-1627>`_ Dependency versions uplift because of vulnerability issues + - `CPS-1629 <https://lf-onap.atlassian.net/browse/CPS-1629>`_ Ordering of leaf elements to support combination of AND/OR in cps-path + - `CPS-1637 <https://lf-onap.atlassian.net/browse/CPS-1637>`_ Extend hazelcast to work on kubernetes Version: 3.3.0 ============== @@ -791,8 +857,8 @@ Bug Fixes Features -------- - - `CPS-1215 <https://jira.onap.org/browse/CPS-1215>`_ Add OR operation for CPS Path - - `CPS-1617 <https://jira.onap.org/browse/CPS-1617>`_ Use cascade delete in fragments table + - `CPS-1215 <https://lf-onap.atlassian.net/browse/CPS-1215>`_ Add OR operation for CPS Path + - `CPS-1617 <https://lf-onap.atlassian.net/browse/CPS-1617>`_ Use cascade delete in fragments table .. ====================== .. * * * LONDON * * * @@ -821,12 +887,12 @@ Release Data Bug Fixes --------- 3.2.6 - - `CPS-1526 <https://jira.onap.org/browse/CPS-1526>`_ Fix response message for PATCH operation - - `CPS-1563 <https://jira.onap.org/browse/CPS-1563>`_ Fix 500 response error on id-searches with empty parameters + - `CPS-1526 <https://lf-onap.atlassian.net/browse/CPS-1526>`_ Fix response message for PATCH operation + - `CPS-1563 <https://lf-onap.atlassian.net/browse/CPS-1563>`_ Fix 500 response error on id-searches with empty parameters Features -------- - - `CPS-1396 <https://jira.onap.org/browse/CPS-1396>`_ Query data nodes across all anchors under one dataspace + - `CPS-1396 <https://lf-onap.atlassian.net/browse/CPS-1396>`_ Query data nodes across all anchors under one dataspace Version: 3.2.5 ============== @@ -851,7 +917,7 @@ Release Data Bug Fixes --------- 3.2.5 - - `CPS-1537 <https://jira.onap.org/browse/CPS-1537>`_ Introduce control switch for model loader functionality. + - `CPS-1537 <https://lf-onap.atlassian.net/browse/CPS-1537>`_ Introduce control switch for model loader functionality. Features -------- @@ -880,8 +946,8 @@ Release Data Bug Fixes --------- 3.2.4 - - `CPS-1533 <https://jira.onap.org/browse/CPS-1533>`_ Fix for Temp tables cause Out of shared memory errors in Postgres - - `CPS-1537 <https://jira.onap.org/browse/CPS-1537>`_ NCMP failed to start due to issue in SubscriptionModelLoader + - `CPS-1533 <https://lf-onap.atlassian.net/browse/CPS-1533>`_ Fix for Temp tables cause Out of shared memory errors in Postgres + - `CPS-1537 <https://lf-onap.atlassian.net/browse/CPS-1537>`_ NCMP failed to start due to issue in SubscriptionModelLoader Features -------- @@ -910,12 +976,12 @@ Release Data Bug Fixes --------- 3.2.3 - - `CPS-1494 <https://jira.onap.org/browse/CPS-1494>`_ NCMP Inventory Performance Improvements + - `CPS-1494 <https://lf-onap.atlassian.net/browse/CPS-1494>`_ NCMP Inventory Performance Improvements Features -------- - - `CPS-1401 <https://jira.onap.org/browse/CPS-1401>`_ Added V2 of Get Data Node API,support to retrieve all data nodes under an anchor - - `CPS-1502 <https://jira.onap.org/browse/CPS-1502>`_ Delete Performance Improvements + - `CPS-1401 <https://lf-onap.atlassian.net/browse/CPS-1401>`_ Added V2 of Get Data Node API,support to retrieve all data nodes under an anchor + - `CPS-1502 <https://lf-onap.atlassian.net/browse/CPS-1502>`_ Delete Performance Improvements Version: 3.2.2 ============== @@ -940,7 +1006,7 @@ Release Data Bug Fixes --------- 3.2.2 - - `CPS-1173 <https://jira.onap.org/browse/CPS-1173>`_ Delete Performance Improvements. + - `CPS-1173 <https://lf-onap.atlassian.net/browse/CPS-1173>`_ Delete Performance Improvements. Features -------- @@ -969,40 +1035,40 @@ Release Data Features -------- 3.2.1 - - `CPS-341 <https://jira.onap.org/browse/CPS-341>`_ Added support for multiple data tree instances under 1 anchor. - - `CPS-1002 <https://jira.onap.org/browse/CPS-1002>`_ Add CPS-E-05 endpoint for 'Query data, NCMP-Operational Datastore' using cpsPaths - - `CPS-1182 <https://jira.onap.org/browse/CPS-1182>`_ Upgrade Opendaylight - - `CPS-1185 <https://jira.onap.org/browse/CPS-1185>`_ Get all dataspaces. - - `CPS-1186 <https://jira.onap.org/browse/CPS-1186>`_ Get single dataspace. - - `CPS-1187 <https://jira.onap.org/browse/CPS-1187>`_ Added API to get all schema sets for a given dataspace. - - `CPS-1236 <https://jira.onap.org/browse/CPS-1236>`_ DMI audit support for NCMP: Filter on any properties of CM Handles - - `CPS-1257 <https://jira.onap.org/browse/CPS-1257>`_ Added support for application/xml Content-Type (write only). - - `CPS-1381 <https://jira.onap.org/browse/CPS-1381>`_ Query large outputs using limit/depth/pagination - - `CPS-1421 <https://jira.onap.org/browse/CPS-1421>`_ Optimized query for large number of hits with descendants. - - `CPS-1422 <https://jira.onap.org/browse/CPS-1422>`_ Fetch CM handles by collection of xpaths (CPS Core) - - `CPS-1424 <https://jira.onap.org/browse/CPS-1424>`_ Updating CmHandleStates using batch operation - - `CPS-1439 <https://jira.onap.org/browse/CPS-1439>`_ Use native query to delete data nodes + - `CPS-341 <https://lf-onap.atlassian.net/browse/CPS-341>`_ Added support for multiple data tree instances under 1 anchor. + - `CPS-1002 <https://lf-onap.atlassian.net/browse/CPS-1002>`_ Add CPS-E-05 endpoint for 'Query data, NCMP-Operational Datastore' using cpsPaths + - `CPS-1182 <https://lf-onap.atlassian.net/browse/CPS-1182>`_ Upgrade Opendaylight + - `CPS-1185 <https://lf-onap.atlassian.net/browse/CPS-1185>`_ Get all dataspaces. + - `CPS-1186 <https://lf-onap.atlassian.net/browse/CPS-1186>`_ Get single dataspace. + - `CPS-1187 <https://lf-onap.atlassian.net/browse/CPS-1187>`_ Added API to get all schema sets for a given dataspace. + - `CPS-1236 <https://lf-onap.atlassian.net/browse/CPS-1236>`_ DMI audit support for NCMP: Filter on any properties of CM Handles + - `CPS-1257 <https://lf-onap.atlassian.net/browse/CPS-1257>`_ Added support for application/xml Content-Type (write only). + - `CPS-1381 <https://lf-onap.atlassian.net/browse/CPS-1381>`_ Query large outputs using limit/depth/pagination + - `CPS-1421 <https://lf-onap.atlassian.net/browse/CPS-1421>`_ Optimized query for large number of hits with descendants. + - `CPS-1422 <https://lf-onap.atlassian.net/browse/CPS-1422>`_ Fetch CM handles by collection of xpaths (CPS Core) + - `CPS-1424 <https://lf-onap.atlassian.net/browse/CPS-1424>`_ Updating CmHandleStates using batch operation + - `CPS-1439 <https://lf-onap.atlassian.net/browse/CPS-1439>`_ Use native query to delete data nodes Bug Fixes --------- 3.2.1 - - `CPS-1171 <https://jira.onap.org/browse/CPS-1171>`_ Optimized retrieval of data nodes with many descendants. - - `CPS-1288 <https://jira.onap.org/browse/CPS-1288>`_ Hazelcast TTL for IMap is not working - - `CPS-1289 <https://jira.onap.org/browse/CPS-1289>`_ Getting wrong error code for create node api - - `CPS-1326 <https://jira.onap.org/browse/CPS-1326>`_ Creation of DataNodeBuilder with module name prefix is very slow - - `CPS-1344 <https://jira.onap.org/browse/CPS-1344>`_ Top level container (prefix) is not always the first module - - `CPS-1350 <https://jira.onap.org/browse/CPS-1350>`_ Add Basic Authentication to CPS/NCMP OpenAPI Definitions. - - `CPS-1352 <https://jira.onap.org/browse/CPS-1352>`_ Handle YangChoiceNode in right format. - - `CPS-1409 <https://jira.onap.org/browse/CPS-1409>`_ Fix Delete uses case with '/' in path. - - `CPS-1433 <https://jira.onap.org/browse/CPS-1433>`_ Fix to allow posting data with '/' key fields. - - `CPS-1442 <https://jira.onap.org/browse/CPS-1442>`_ CPS PATCH operation does not merge existing data - - `CPS-1446 <https://jira.onap.org/browse/CPS-1446>`_ Locked cmhandles and ready to locked state transitions causing long cmHandle discovery - - `CPS-1457 <https://jira.onap.org/browse/CPS-1457>`_ CpsDataPersistenceService#getDataNodes uses non-normalized xpaths - - `CPS-1458 <https://jira.onap.org/browse/CPS-1458>`_ CpsDataPersistenceService#getDataNodes does not handle root xpath - - `CPS-1460 <https://jira.onap.org/browse/CPS-1460>`_ CPS Path Processing Performance Test duration is too low + - `CPS-1171 <https://lf-onap.atlassian.net/browse/CPS-1171>`_ Optimized retrieval of data nodes with many descendants. + - `CPS-1288 <https://lf-onap.atlassian.net/browse/CPS-1288>`_ Hazelcast TTL for IMap is not working + - `CPS-1289 <https://lf-onap.atlassian.net/browse/CPS-1289>`_ Getting wrong error code for create node api + - `CPS-1326 <https://lf-onap.atlassian.net/browse/CPS-1326>`_ Creation of DataNodeBuilder with module name prefix is very slow + - `CPS-1344 <https://lf-onap.atlassian.net/browse/CPS-1344>`_ Top level container (prefix) is not always the first module + - `CPS-1350 <https://lf-onap.atlassian.net/browse/CPS-1350>`_ Add Basic Authentication to CPS/NCMP OpenAPI Definitions. + - `CPS-1352 <https://lf-onap.atlassian.net/browse/CPS-1352>`_ Handle YangChoiceNode in right format. + - `CPS-1409 <https://lf-onap.atlassian.net/browse/CPS-1409>`_ Fix Delete uses case with '/' in path. + - `CPS-1433 <https://lf-onap.atlassian.net/browse/CPS-1433>`_ Fix to allow posting data with '/' key fields. + - `CPS-1442 <https://lf-onap.atlassian.net/browse/CPS-1442>`_ CPS PATCH operation does not merge existing data + - `CPS-1446 <https://lf-onap.atlassian.net/browse/CPS-1446>`_ Locked cmhandles and ready to locked state transitions causing long cmHandle discovery + - `CPS-1457 <https://lf-onap.atlassian.net/browse/CPS-1457>`_ CpsDataPersistenceService#getDataNodes uses non-normalized xpaths + - `CPS-1458 <https://lf-onap.atlassian.net/browse/CPS-1458>`_ CpsDataPersistenceService#getDataNodes does not handle root xpath + - `CPS-1460 <https://lf-onap.atlassian.net/browse/CPS-1460>`_ CPS Path Processing Performance Test duration is too low 3.2.0 - - `CPS-1312 <https://jira.onap.org/browse/CPS-1312>`_ CPS(/NCMP) does not have version control. + - `CPS-1312 <https://lf-onap.atlassian.net/browse/CPS-1312>`_ CPS(/NCMP) does not have version control. Known Limitations, Issues and Workarounds ----------------------------------------- @@ -1041,8 +1107,8 @@ Release Data Bug Fixes --------- - - `CPS-1265 <https://jira.onap.org/browse/CPS-1265>`_ Revision field should not be required (NotNull) on cps-ri YangResourceEntity - - `CPS-1294 <https://jira.onap.org/browse/CPS-1294>`_ Kafka communication fault caused cmHandle registration error + - `CPS-1265 <https://lf-onap.atlassian.net/browse/CPS-1265>`_ Revision field should not be required (NotNull) on cps-ri YangResourceEntity + - `CPS-1294 <https://lf-onap.atlassian.net/browse/CPS-1294>`_ Kafka communication fault caused cmHandle registration error Version: 3.1.3 ============== @@ -1121,7 +1187,7 @@ Security Notes *Fixed Security Issues* - - `CPS-1226 <https://jira.onap.org/browse/CPS-1226>`_ Security bug in the logs + - `CPS-1226 <https://lf-onap.atlassian.net/browse/CPS-1226>`_ Security bug in the logs Version: 3.1.0 ============== @@ -1145,48 +1211,48 @@ Release Data Features -------- - - `CPS-340 <https://jira.onap.org/browse/CPS-340>`_ Patch and update the root data node - - `CPS-575 <https://jira.onap.org/browse/CPS-575>`_ Write data for cmHandle using ncmp-datastores:passthrough-running (NCMP.) - - `CPS-731 <https://jira.onap.org/browse/CPS-731>`_ Query based on Public CM Properties - - `CPS-828 <https://jira.onap.org/browse/CPS-828>`_ Async: NCMP Rest impl. including Request ID generation - - `CPS-829 <https://jira.onap.org/browse/CPS-829>`_ Async: Internal message topic incl. basic producer & Consumer - - `CPS-830 <https://jira.onap.org/browse/CPS-830>`_ DMI-NCMP Asynchronously Publish Response Event to Client Topic - - `CPS-869 <https://jira.onap.org/browse/CPS-869>`_ Apply Standardized logging fields to adhere to ONAP Best practice REQ-1072 - - `CPS-870 <https://jira.onap.org/browse/CPS-870>`_ Align CPS-Core output with SDN-C output (add module name) - - `CPS-875 <https://jira.onap.org/browse/CPS-875>`_ CM Handle State: Watchdog-process that syncs 'ADVISED' CM Handles - - `CPS-877 <https://jira.onap.org/browse/CPS-877>`_ CM Handle State: Exclude any CM-Handles from queries/operations that are not in state 'READY' - - `CPS-899 <https://jira.onap.org/browse/CPS-899>`_ Start and stop sessions on Java API - - `CPS-909 <https://jira.onap.org/browse/CPS-909>`_ Separate NCMP endpoint for ch/{cm-handle}/properties and ch/{cm-handle}/state - - `CPS-917 <https://jira.onap.org/browse/CPS-917>`_ Structured Errors response for passthrough use-cases in NCMP - - `CPS-953 <https://jira.onap.org/browse/CPS-953>`_ Update maven deploy plugin version - - `CPS-977 <https://jira.onap.org/browse/CPS-977>`_ Query CM Handles using CpsPath - - `CPS-1000 <https://jira.onap.org/browse/CPS-1000>`_ Create Data Synchronization watchdog - - `CPS-1016 <https://jira.onap.org/browse/CPS-1016>`_ Merge 2 'query' end points in NCMP - - `CPS-1034 <https://jira.onap.org/browse/CPS-1034>`_ Publish lifecycle events for ADVISED , READY and LOCKED state transition" - - `CPS-1064 <https://jira.onap.org/browse/CPS-1064>`_ Support retrieval of YANG module sources for CM handle on the NCMP interface - - `CPS-1099 <https://jira.onap.org/browse/CPS-1099>`_ Expose simplified 'external' lock reason enum state over REST interface - - `CPS-1101 <https://jira.onap.org/browse/CPS-1101>`_ Introducing the DELETING and DELETED Cmhandle State - - `CPS-1102 <https://jira.onap.org/browse/CPS-1102>`_ Register the Cmhandle Sends Advised State notification. - - `CPS-1133 <https://jira.onap.org/browse/CPS-1133>`_ Enable/Disable Data Sync for Cm Handle - - `CPS-1136 <https://jira.onap.org/browse/CPS-1136>`_ DMI Audit Support (get all CM Handles for a registered DMI) + - `CPS-340 <https://lf-onap.atlassian.net/browse/CPS-340>`_ Patch and update the root data node + - `CPS-575 <https://lf-onap.atlassian.net/browse/CPS-575>`_ Write data for cmHandle using ncmp-datastores:passthrough-running (NCMP.) + - `CPS-731 <https://lf-onap.atlassian.net/browse/CPS-731>`_ Query based on Public CM Properties + - `CPS-828 <https://lf-onap.atlassian.net/browse/CPS-828>`_ Async: NCMP Rest impl. including Request ID generation + - `CPS-829 <https://lf-onap.atlassian.net/browse/CPS-829>`_ Async: Internal message topic incl. basic producer & Consumer + - `CPS-830 <https://lf-onap.atlassian.net/browse/CPS-830>`_ DMI-NCMP Asynchronously Publish Response Event to Client Topic + - `CPS-869 <https://lf-onap.atlassian.net/browse/CPS-869>`_ Apply Standardized logging fields to adhere to ONAP Best practice REQ-1072 + - `CPS-870 <https://lf-onap.atlassian.net/browse/CPS-870>`_ Align CPS-Core output with SDN-C output (add module name) + - `CPS-875 <https://lf-onap.atlassian.net/browse/CPS-875>`_ CM Handle State: Watchdog-process that syncs 'ADVISED' CM Handles + - `CPS-877 <https://lf-onap.atlassian.net/browse/CPS-877>`_ CM Handle State: Exclude any CM-Handles from queries/operations that are not in state 'READY' + - `CPS-899 <https://lf-onap.atlassian.net/browse/CPS-899>`_ Start and stop sessions on Java API + - `CPS-909 <https://lf-onap.atlassian.net/browse/CPS-909>`_ Separate NCMP endpoint for ch/{cm-handle}/properties and ch/{cm-handle}/state + - `CPS-917 <https://lf-onap.atlassian.net/browse/CPS-917>`_ Structured Errors response for passthrough use-cases in NCMP + - `CPS-953 <https://lf-onap.atlassian.net/browse/CPS-953>`_ Update maven deploy plugin version + - `CPS-977 <https://lf-onap.atlassian.net/browse/CPS-977>`_ Query CM Handles using CpsPath + - `CPS-1000 <https://lf-onap.atlassian.net/browse/CPS-1000>`_ Create Data Synchronization watchdog + - `CPS-1016 <https://lf-onap.atlassian.net/browse/CPS-1016>`_ Merge 2 'query' end points in NCMP + - `CPS-1034 <https://lf-onap.atlassian.net/browse/CPS-1034>`_ Publish lifecycle events for ADVISED , READY and LOCKED state transition" + - `CPS-1064 <https://lf-onap.atlassian.net/browse/CPS-1064>`_ Support retrieval of YANG module sources for CM handle on the NCMP interface + - `CPS-1099 <https://lf-onap.atlassian.net/browse/CPS-1099>`_ Expose simplified 'external' lock reason enum state over REST interface + - `CPS-1101 <https://lf-onap.atlassian.net/browse/CPS-1101>`_ Introducing the DELETING and DELETED Cmhandle State + - `CPS-1102 <https://lf-onap.atlassian.net/browse/CPS-1102>`_ Register the Cmhandle Sends Advised State notification. + - `CPS-1133 <https://lf-onap.atlassian.net/browse/CPS-1133>`_ Enable/Disable Data Sync for Cm Handle + - `CPS-1136 <https://lf-onap.atlassian.net/browse/CPS-1136>`_ DMI Audit Support (get all CM Handles for a registered DMI) Bug Fixes --------- - - `CPS-896 <https://jira.onap.org/browse/CPS-896>`_ CM Handle Registration Process only partially completes when exception is thrown - - `CPS-957 <https://jira.onap.org/browse/CPS-957>`_ NCMP: fix getResourceDataForPassthroughOperational endpoint - - `CPS-1020 <https://jira.onap.org/browse/CPS-1020>`_ DuplicatedYangResourceException error at parallel cmHandle registration - - `CPS-1056 <https://jira.onap.org/browse/CPS-1056>`_ Wrong error response format in case of Dmi plugin error - - `CPS-1067 <https://jira.onap.org/browse/CPS-1067>`_ NCMP returns 500 error on searches endpoint when No DMI Handles registered - - `CPS-1085 <https://jira.onap.org/browse/CPS-1085>`_ Performance degradation on ncmp/v1/ch/searches endpoint - - `CPS-1088 <https://jira.onap.org/browse/CPS-1088>`_ Kafka consumer can not be turned off - - `CPS-1097 <https://jira.onap.org/browse/CPS-1097>`_ Unable to change state from LOCKED to ADVISED - - `CPS-1126 <https://jira.onap.org/browse/CPS-1126>`_ CmHandle creation performance degradation - - `CPS-1175 <https://jira.onap.org/browse/CPS-1175>`_ Incorrect response when empty body executed for cmhandle id-searches - - `CPS-1179 <https://jira.onap.org/browse/CPS-1179>`_ Node API - GET method returns invalid response when identifier contains '/' - - `CPS-1212 <https://jira.onap.org/browse/CPS-1212>`_ Additional Properties for CM Handles not included when send to DMI Plugin - - `CPS-1217 <https://jira.onap.org/browse/CPS-1217>`_ Searches endpoint gives back empty list however there are already available cmhandles - - `CPS-1218 <https://jira.onap.org/browse/CPS-1218>`_ NCMP logs are flooded with SyncUtils logs + - `CPS-896 <https://lf-onap.atlassian.net/browse/CPS-896>`_ CM Handle Registration Process only partially completes when exception is thrown + - `CPS-957 <https://lf-onap.atlassian.net/browse/CPS-957>`_ NCMP: fix getResourceDataForPassthroughOperational endpoint + - `CPS-1020 <https://lf-onap.atlassian.net/browse/CPS-1020>`_ DuplicatedYangResourceException error at parallel cmHandle registration + - `CPS-1056 <https://lf-onap.atlassian.net/browse/CPS-1056>`_ Wrong error response format in case of Dmi plugin error + - `CPS-1067 <https://lf-onap.atlassian.net/browse/CPS-1067>`_ NCMP returns 500 error on searches endpoint when No DMI Handles registered + - `CPS-1085 <https://lf-onap.atlassian.net/browse/CPS-1085>`_ Performance degradation on ncmp/v1/ch/searches endpoint + - `CPS-1088 <https://lf-onap.atlassian.net/browse/CPS-1088>`_ Kafka consumer can not be turned off + - `CPS-1097 <https://lf-onap.atlassian.net/browse/CPS-1097>`_ Unable to change state from LOCKED to ADVISED + - `CPS-1126 <https://lf-onap.atlassian.net/browse/CPS-1126>`_ CmHandle creation performance degradation + - `CPS-1175 <https://lf-onap.atlassian.net/browse/CPS-1175>`_ Incorrect response when empty body executed for cmhandle id-searches + - `CPS-1179 <https://lf-onap.atlassian.net/browse/CPS-1179>`_ Node API - GET method returns invalid response when identifier contains '/' + - `CPS-1212 <https://lf-onap.atlassian.net/browse/CPS-1212>`_ Additional Properties for CM Handles not included when send to DMI Plugin + - `CPS-1217 <https://lf-onap.atlassian.net/browse/CPS-1217>`_ Searches endpoint gives back empty list however there are already available cmhandles + - `CPS-1218 <https://lf-onap.atlassian.net/browse/CPS-1218>`_ NCMP logs are flooded with SyncUtils logs Known Limitations, Issues and Workarounds ----------------------------------------- @@ -1209,7 +1275,7 @@ Security Notes *Fixed Security Issues* - - `CPS-963 <https://jira.onap.org/browse/CPS-963>`_ Liquibase has got serious vulnerability, upgrade required + - `CPS-963 <https://lf-onap.atlassian.net/browse/CPS-963>`_ Liquibase has got serious vulnerability, upgrade required *Known Security Issues* @@ -1241,7 +1307,7 @@ Release Data Bug Fixes --------- - - `CPS-961 <https://jira.onap.org/browse/CPS-961>`_ Updated ANTLR compiler version to 4.9.2 to be compatible with runtime version + - `CPS-961 <https://lf-onap.atlassian.net/browse/CPS-961>`_ Updated ANTLR compiler version to 4.9.2 to be compatible with runtime version Version: 3.0.0 ============== @@ -1265,31 +1331,31 @@ Release Data Features -------- - - `CPS-559 <https://jira.onap.org/browse/CPS-559>`_ Define response objects (schemas) in cps-ncmp - - `CPS-636 <https://jira.onap.org/browse/CPS-636>`_ Update operation for datastore pass through running - - `CPS-638 <https://jira.onap.org/browse/CPS-638>`_ Delete operation for datastore pass through running - - `CPS-677 <https://jira.onap.org/browse/CPS-677>`_ Support 'public' Cm Handle Properties - - `CPS-741 <https://jira.onap.org/browse/CPS-741>`_ Re sync after removing cm handles - - `CPS-777 <https://jira.onap.org/browse/CPS-777>`_ Ensure all DMI operations use POST method - - `CPS-780 <https://jira.onap.org/browse/CPS-780>`_ Add examples for parameters, request and response in openapi yaml for cps-core - - `CPS-789 <https://jira.onap.org/browse/CPS-789>`_ CPS Data Updated Event Schema V2 to support delete operation - - `CPS-791 <https://jira.onap.org/browse/CPS-791>`_ CPS-Core sends delete notification event - - `CPS-817 <https://jira.onap.org/browse/CPS-817>`_ Create Endpoint For Get Cm Handles (incl. public properties) By Name - - `CPS-837 <https://jira.onap.org/browse/CPS-837>`_ Add Remove and Update properties (DMI and Public) as part of CM Handle Registration update + - `CPS-559 <https://lf-onap.atlassian.net/browse/CPS-559>`_ Define response objects (schemas) in cps-ncmp + - `CPS-636 <https://lf-onap.atlassian.net/browse/CPS-636>`_ Update operation for datastore pass through running + - `CPS-638 <https://lf-onap.atlassian.net/browse/CPS-638>`_ Delete operation for datastore pass through running + - `CPS-677 <https://lf-onap.atlassian.net/browse/CPS-677>`_ Support 'public' Cm Handle Properties + - `CPS-741 <https://lf-onap.atlassian.net/browse/CPS-741>`_ Re sync after removing cm handles + - `CPS-777 <https://lf-onap.atlassian.net/browse/CPS-777>`_ Ensure all DMI operations use POST method + - `CPS-780 <https://lf-onap.atlassian.net/browse/CPS-780>`_ Add examples for parameters, request and response in openapi yaml for cps-core + - `CPS-789 <https://lf-onap.atlassian.net/browse/CPS-789>`_ CPS Data Updated Event Schema V2 to support delete operation + - `CPS-791 <https://lf-onap.atlassian.net/browse/CPS-791>`_ CPS-Core sends delete notification event + - `CPS-817 <https://lf-onap.atlassian.net/browse/CPS-817>`_ Create Endpoint For Get Cm Handles (incl. public properties) By Name + - `CPS-837 <https://lf-onap.atlassian.net/browse/CPS-837>`_ Add Remove and Update properties (DMI and Public) as part of CM Handle Registration update Bug Fixes --------- - - `CPS-762 <https://jira.onap.org/browse/CPS-762>`_ Query cm handles for module names returns incorrect cm handle identifiers - - `CPS-788 <https://jira.onap.org/browse/CPS-788>`_ Yang Resource formatting is incorrect - - `CPS-783 <https://jira.onap.org/browse/CPS-783>`_ Remove cm handle does not completely remove all cm handle information - - `CPS-841 <https://jira.onap.org/browse/CPS-841>`_ Upgrade log4j to 2.17.1 as recommended by ONAP SECCOM - - `CPS-856 <https://jira.onap.org/browse/CPS-856>`_ Retry mechanism not working for concurrent CmHandle registration - - `CPS-867 <https://jira.onap.org/browse/CPS-867>`_ Database port made configurable through env variable DB_PORT - - `CPS-886 <https://jira.onap.org/browse/CPS-886>`_ Fragment handling decreasing performance for large number of cmHandles - - `CPS-887 <https://jira.onap.org/browse/CPS-887>`_ Increase performance of cmHandle registration for large number of schema sets in DB - - `CPS-892 <https://jira.onap.org/browse/CPS-892>`_ Fixed the response code during CM-Handle Registration from 201 CREATED to 204 NO_CONTENT - - `CPS-893 <https://jira.onap.org/browse/CPS-893>`_ NCMP Java API depends on NCMP-Rest-API (cyclic) through json properties on Java API + - `CPS-762 <https://lf-onap.atlassian.net/browse/CPS-762>`_ Query cm handles for module names returns incorrect cm handle identifiers + - `CPS-788 <https://lf-onap.atlassian.net/browse/CPS-788>`_ Yang Resource formatting is incorrect + - `CPS-783 <https://lf-onap.atlassian.net/browse/CPS-783>`_ Remove cm handle does not completely remove all cm handle information + - `CPS-841 <https://lf-onap.atlassian.net/browse/CPS-841>`_ Upgrade log4j to 2.17.1 as recommended by ONAP SECCOM + - `CPS-856 <https://lf-onap.atlassian.net/browse/CPS-856>`_ Retry mechanism not working for concurrent CmHandle registration + - `CPS-867 <https://lf-onap.atlassian.net/browse/CPS-867>`_ Database port made configurable through env variable DB_PORT + - `CPS-886 <https://lf-onap.atlassian.net/browse/CPS-886>`_ Fragment handling decreasing performance for large number of cmHandles + - `CPS-887 <https://lf-onap.atlassian.net/browse/CPS-887>`_ Increase performance of cmHandle registration for large number of schema sets in DB + - `CPS-892 <https://lf-onap.atlassian.net/browse/CPS-892>`_ Fixed the response code during CM-Handle Registration from 201 CREATED to 204 NO_CONTENT + - `CPS-893 <https://lf-onap.atlassian.net/browse/CPS-893>`_ NCMP Java API depends on NCMP-Rest-API (cyclic) through json properties on Java API Known Limitations, Issues and Workarounds ----------------------------------------- @@ -1297,10 +1363,10 @@ Known Limitations, Issues and Workarounds *System Limitations* Null can no longer be passed within the dmi plugin service names when registering a cm handle, as part of -`CPS-837 <https://jira.onap.org/browse/CPS-837>`_ null is now used to indicate if a property should be removed as part +`CPS-837 <https://lf-onap.atlassian.net/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>`_ +The Absolute path to list with integer key will not work. Please refer `CPS-961 <https://lf-onap.atlassian.net/browse/CPS-961>`_ for more information. *Known Vulnerabilities* @@ -1350,8 +1416,8 @@ Release Data Bug Fixes --------- - - `CPS-879 <https://jira.onap.org/browse/CPS-879>`_ Fix docker compose for csit test. - - `CPS-873 <https://jira.onap.org/browse/CPS-873>`_ Fix intermittent circular dependency error when the application starts. + - `CPS-879 <https://lf-onap.atlassian.net/browse/CPS-879>`_ Fix docker compose for csit test. + - `CPS-873 <https://lf-onap.atlassian.net/browse/CPS-873>`_ Fix intermittent circular dependency error when the application starts. Version: 2.0.3 ============== @@ -1376,7 +1442,7 @@ Release Data Bug Fixes --------- - - `CPS-841 <https://jira.onap.org/browse/CPS-841>`_ Update log4j version to 2.17.1 due to security vulnerability + - `CPS-841 <https://lf-onap.atlassian.net/browse/CPS-841>`_ Update log4j version to 2.17.1 due to security vulnerability Version: 2.0.2 ============== @@ -1401,7 +1467,7 @@ Release Data Bug Fixes --------- - - `CPS-820 <https://jira.onap.org/browse/CPS-820>`_ Update log4j version due to security vulnerability + - `CPS-820 <https://lf-onap.atlassian.net/browse/CPS-820>`_ Update log4j version due to security vulnerability Version: 2.0.1 ============== @@ -1426,10 +1492,10 @@ Release Data Bug Fixes --------- - - `CPS-594 <https://jira.onap.org/browse/CPS-594>`_ SQL ConstraintViolationException when updating the list node element using PATCH List node API - - `CPS-653 <https://jira.onap.org/browse/CPS-653>`_ cmHandleProperties not supported by dmi in fetch modules - - `CPS-673 <https://jira.onap.org/browse/CPS-673>`_ Improvement and cleanup for CPS Core charts - - `CPS-691 <https://jira.onap.org/browse/CPS-691>`_ NCMP no master index label on index documentation page + - `CPS-594 <https://lf-onap.atlassian.net/browse/CPS-594>`_ SQL ConstraintViolationException when updating the list node element using PATCH List node API + - `CPS-653 <https://lf-onap.atlassian.net/browse/CPS-653>`_ cmHandleProperties not supported by dmi in fetch modules + - `CPS-673 <https://lf-onap.atlassian.net/browse/CPS-673>`_ Improvement and cleanup for CPS Core charts + - `CPS-691 <https://lf-onap.atlassian.net/browse/CPS-691>`_ NCMP no master index label on index documentation page Known Limitations, Issues and Workarounds ----------------------------------------- @@ -1440,7 +1506,7 @@ Limitations to the amount of child nodes that can be added to the fix above. The *Known Vulnerabilities* - - `CPS-725 <https://jira.onap.org/browse/CPS-725>`_ fix sample docker compose of cps/ncmp and onap dmi plugin + - `CPS-725 <https://lf-onap.atlassian.net/browse/CPS-725>`_ fix sample docker compose of cps/ncmp and onap dmi plugin *Workarounds* @@ -1451,7 +1517,7 @@ Security Notes *Fixed Security Issues* - - `CPS-581 <https://jira.onap.org/browse/CPS-581>`_ Remove security vulnerabilities + - `CPS-581 <https://lf-onap.atlassian.net/browse/CPS-581>`_ Remove security vulnerabilities *Known Security Issues* @@ -1503,32 +1569,32 @@ Software Deliverables Bug Fixes --------- - - `CPS-310 <https://jira.onap.org/browse/CPS-310>`_ Data schema migration from Honolulu to Istanbul is failing - - `CPS-316 <https://jira.onap.org/browse/CPS-316>`_ Xpath cannot be created for augmentation data node - - `CPS-336 <https://jira.onap.org/browse/CPS-336>`_ Ends-with functionality in cpsPath does not conform with standard xPath behavior - - `CPS-345 <https://jira.onap.org/browse/CPS-345>`_ Leaf String value comparison matches mix of single and double quotes - - `CPS-357 <https://jira.onap.org/browse/CPS-357>`_ cps-review-verification-maven-master Jenkins job is failing when running csit test - - `CPS-367 <https://jira.onap.org/browse/CPS-367>`_ Get descendent does not support xpaths that end in list values - - `CPS-377 <https://jira.onap.org/browse/CPS-377>`_ Init ran model validation is failing error details are not provided - - `CPS-422 <https://jira.onap.org/browse/CPS-422>`_ REST 404 response returned instead of 400 for POST/PUT/PATCH request types - - `CPS-450 <https://jira.onap.org/browse/CPS-450>`_ Datanode query using full path to node causes NPE - - `CPS-451 <https://jira.onap.org/browse/CPS-451>`_ cps-ran-schema-model@2021-01-28.yang missing root container - - `CPS-464 <https://jira.onap.org/browse/CPS-464>`_ Request to update node leaves (patch) responds with Internal Server Error - - `CPS-465 <https://jira.onap.org/browse/CPS-465>`_ Request to update node leaves (patch) responds with json parsing failure - - `CPS-466 <https://jira.onap.org/browse/CPS-466>`_ Concurrent requests to create schema sets for the same yang model are not supported - - `CPS-479 <https://jira.onap.org/browse/CPS-479>`_ Get Nodes API does not always return the object from the root - - `CPS-500 <https://jira.onap.org/browse/CPS-500>`_ Special Character Limitations of cpsPath Queries - - `CPS-501 <https://jira.onap.org/browse/CPS-501>`_ Put DataNode API has missing transaction and error handling for concurrency issues - - `CPS-524 <https://jira.onap.org/browse/CPS-524>`_ Issue with CPSData API to add an item to an existing list node - - `CPS-560 <https://jira.onap.org/browse/CPS-560>`_ Response from cps query using text() contains escape characters - - `CPS-566 <https://jira.onap.org/browse/CPS-566>`_ Can't access grandparent node through ancestor axis - - `CPS-573 <https://jira.onap.org/browse/CPS-573>`_ /v1/ch/PNFDemo1/modules returning 401 unauthorised. - - `CPS-587 <https://jira.onap.org/browse/CPS-587>`_ cps-ncmp-service NullpointerException when DmiPluginRegistration has no additionProperties - - `CPS-591 <https://jira.onap.org/browse/CPS-591>`_ CPS-Core Leaf stored as integer is being returned from DB as float - - `CPS-601 <https://jira.onap.org/browse/CPS-601>`_ CPS swagger-ui does not show NCMP endpoints - - `CPS-616 <https://jira.onap.org/browse/CPS-616>`_ NCMP base path does not conform to agreed API URL - - `CPS-630 <https://jira.onap.org/browse/CPS-630>`_ Incorrect information sent when same anchor is updated faster than notification service processes - - `CPS-635 <https://jira.onap.org/browse/CPS-635>`_ Module Resource call does not include body + - `CPS-310 <https://lf-onap.atlassian.net/browse/CPS-310>`_ Data schema migration from Honolulu to Istanbul is failing + - `CPS-316 <https://lf-onap.atlassian.net/browse/CPS-316>`_ Xpath cannot be created for augmentation data node + - `CPS-336 <https://lf-onap.atlassian.net/browse/CPS-336>`_ Ends-with functionality in cpsPath does not conform with standard xPath behavior + - `CPS-345 <https://lf-onap.atlassian.net/browse/CPS-345>`_ Leaf String value comparison matches mix of single and double quotes + - `CPS-357 <https://lf-onap.atlassian.net/browse/CPS-357>`_ cps-review-verification-maven-master Jenkins job is failing when running csit test + - `CPS-367 <https://lf-onap.atlassian.net/browse/CPS-367>`_ Get descendent does not support xpaths that end in list values + - `CPS-377 <https://lf-onap.atlassian.net/browse/CPS-377>`_ Init ran model validation is failing error details are not provided + - `CPS-422 <https://lf-onap.atlassian.net/browse/CPS-422>`_ REST 404 response returned instead of 400 for POST/PUT/PATCH request types + - `CPS-450 <https://lf-onap.atlassian.net/browse/CPS-450>`_ Datanode query using full path to node causes NPE + - `CPS-451 <https://lf-onap.atlassian.net/browse/CPS-451>`_ cps-ran-schema-model@2021-01-28.yang missing root container + - `CPS-464 <https://lf-onap.atlassian.net/browse/CPS-464>`_ Request to update node leaves (patch) responds with Internal Server Error + - `CPS-465 <https://lf-onap.atlassian.net/browse/CPS-465>`_ Request to update node leaves (patch) responds with json parsing failure + - `CPS-466 <https://lf-onap.atlassian.net/browse/CPS-466>`_ Concurrent requests to create schema sets for the same yang model are not supported + - `CPS-479 <https://lf-onap.atlassian.net/browse/CPS-479>`_ Get Nodes API does not always return the object from the root + - `CPS-500 <https://lf-onap.atlassian.net/browse/CPS-500>`_ Special Character Limitations of cpsPath Queries + - `CPS-501 <https://lf-onap.atlassian.net/browse/CPS-501>`_ Put DataNode API has missing transaction and error handling for concurrency issues + - `CPS-524 <https://lf-onap.atlassian.net/browse/CPS-524>`_ Issue with CPSData API to add an item to an existing list node + - `CPS-560 <https://lf-onap.atlassian.net/browse/CPS-560>`_ Response from cps query using text() contains escape characters + - `CPS-566 <https://lf-onap.atlassian.net/browse/CPS-566>`_ Can't access grandparent node through ancestor axis + - `CPS-573 <https://lf-onap.atlassian.net/browse/CPS-573>`_ /v1/ch/PNFDemo1/modules returning 401 unauthorised. + - `CPS-587 <https://lf-onap.atlassian.net/browse/CPS-587>`_ cps-ncmp-service NullpointerException when DmiPluginRegistration has no additionProperties + - `CPS-591 <https://lf-onap.atlassian.net/browse/CPS-591>`_ CPS-Core Leaf stored as integer is being returned from DB as float + - `CPS-601 <https://lf-onap.atlassian.net/browse/CPS-601>`_ CPS swagger-ui does not show NCMP endpoints + - `CPS-616 <https://lf-onap.atlassian.net/browse/CPS-616>`_ NCMP base path does not conform to agreed API URL + - `CPS-630 <https://lf-onap.atlassian.net/browse/CPS-630>`_ Incorrect information sent when same anchor is updated faster than notification service processes + - `CPS-635 <https://lf-onap.atlassian.net/browse/CPS-635>`_ Module Resource call does not include body This document provides the release notes for Istanbul release. @@ -1552,10 +1618,6 @@ Following CPS components are available with default ONAP/CPS installation. - Postgres Database -Below service components (mS) are available to be deployed on-demand. - - CPS-TBDMT - - Under OOM (Kubernetes) all CPS component containers are deployed as Kubernetes Pods/Deployments/Services into Kubernetes cluster. Known Limitations, Issues and Workarounds @@ -1567,9 +1629,9 @@ Limitations to the amount of child nodes that can be added to the fix above. The *Known Vulnerabilities* - - `CPS-594 <https://jira.onap.org/browse/CPS-594>`_ SQL ConstraintViolationException when updating the list node element using PATCH List node API - - `CPS-653 <https://jira.onap.org/browse/CPS-653>`_ cmHandleProperties not supported by dmi in fetch modules - - `CPS-673 <https://jira.onap.org/browse/CPS-673>`_ Improvement and cleanup for CPS Core charts + - `CPS-594 <https://lf-onap.atlassian.net/browse/CPS-594>`_ SQL ConstraintViolationException when updating the list node element using PATCH List node API + - `CPS-653 <https://lf-onap.atlassian.net/browse/CPS-653>`_ cmHandleProperties not supported by dmi in fetch modules + - `CPS-673 <https://lf-onap.atlassian.net/browse/CPS-673>`_ Improvement and cleanup for CPS Core charts *Workarounds* @@ -1580,11 +1642,11 @@ Security Notes *Fixed Security Issues* - - `CPS-249 <https://jira.onap.org/browse/CPS-249>`_ Exception stack trace is exposed + - `CPS-249 <https://lf-onap.atlassian.net/browse/CPS-249>`_ Exception stack trace is exposed *Known Security Issues* - - `CPS-581 <https://jira.onap.org/browse/CPS-581>`_ Remove security vulnerabilities + - `CPS-581 <https://lf-onap.atlassian.net/browse/CPS-581>`_ Remove security vulnerabilities Test Results ------------ @@ -1617,25 +1679,25 @@ Release Data Bug Fixes --------- - - `CPS-706 <https://jira.onap.org/browse/CPS-706>`_ Get moduleschema/yangresouce endpoint not working - - `CPS-276 <https://jira.onap.org/browse/CPS-276>`_ Improve error reporting for invalid cpsPath on Queries - - `CPS-288 <https://jira.onap.org/browse/CPS-288>`_ Move security configuration to the application module - - `CPS-290 <https://jira.onap.org/browse/CPS-290>`_ Internal Server Error when creating the same data node twice - - `CPS-292 <https://jira.onap.org/browse/CPS-292>`_ Detailed information is missing to explain why data is not compliant with the specified YANG model - - `CPS-300 <https://jira.onap.org/browse/CPS-304>`_ Not able to create data instances for 2 different anchors using the same model - - `CPS-304 <https://jira.onap.org/browse/CPS-304>`_ Use ONAP recommended base Java Docker image - - `CPS-308 <https://jira.onap.org/browse/CPS-308>`_ Not able to upload yang models files greater than 1MB + - `CPS-706 <https://lf-onap.atlassian.net/browse/CPS-706>`_ Get moduleschema/yangresouce endpoint not working + - `CPS-276 <https://lf-onap.atlassian.net/browse/CPS-276>`_ Improve error reporting for invalid cpsPath on Queries + - `CPS-288 <https://lf-onap.atlassian.net/browse/CPS-288>`_ Move security configuration to the application module + - `CPS-290 <https://lf-onap.atlassian.net/browse/CPS-290>`_ Internal Server Error when creating the same data node twice + - `CPS-292 <https://lf-onap.atlassian.net/browse/CPS-292>`_ Detailed information is missing to explain why data is not compliant with the specified YANG model + - `CPS-300 <https://lf-onap.atlassian.net/browse/CPS-304>`_ Not able to create data instances for 2 different anchors using the same model + - `CPS-304 <https://lf-onap.atlassian.net/browse/CPS-304>`_ Use ONAP recommended base Java Docker image + - `CPS-308 <https://lf-onap.atlassian.net/browse/CPS-308>`_ Not able to upload yang models files greater than 1MB Security Notes -------------- *Fixed Security Issues* - - `CPS-249 <https://jira.onap.org/browse/CPS-249>`_ Exception stack trace is exposed + - `CPS-249 <https://lf-onap.atlassian.net/browse/CPS-249>`_ Exception stack trace is exposed *Known Security Issues* - - `Security Waiver <https://wiki.onap.org/display/DW/Honolulu+Exception+Request+for+CPS>`_ Security - Expose external endpoints with https + - `Security Waiver <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16467851/Honolulu+Exception+Request+for+CPS>`_ Security - Expose external endpoints with https Version: 1.0.0 ============== @@ -1665,10 +1727,6 @@ Following CPS components are available with default ONAP/CPS installation. - Postgres Database -Below service components (mS) are available to be deployed on-demand. - - CPS-TBDMT - - Under OOM (Kubernetes) all CPS component containers are deployed as Kubernetes Pods/Deployments/Services into Kubernetes cluster. @@ -1714,10 +1772,10 @@ Software Deliverables Known Limitations, Issues and Workarounds ----------------------------------------- - - `CPS-249 <https://jira.onap.org/browse/CPS-249>`_ Exception stack trace is exposed - - `CPS-264 <https://jira.onap.org/browse/CPS-264>`_ Unique timestamp is missing when tagging docker images. + - `CPS-249 <https://lf-onap.atlassian.net/browse/CPS-249>`_ Exception stack trace is exposed + - `CPS-264 <https://lf-onap.atlassian.net/browse/CPS-264>`_ Unique timestamp is missing when tagging docker images. - Methods exposed on API which are yet not implemented : deleteAnchor, getNodesByDataspace & deleteDataspace. - - `CPS-465 <https://jira.onap.org/browse/CPS-465>`_ & `CPS-464 <https://jira.onap.org/browse/CPS-464>`_ Update data node leaves API does not support updating a list element with compound keys. + - `CPS-465 <https://lf-onap.atlassian.net/browse/CPS-465>`_ & `CPS-464 <https://lf-onap.atlassian.net/browse/CPS-464>`_ Update data node leaves API does not support updating a list element with compound keys. *System Limitations* @@ -1736,7 +1794,7 @@ Security Notes *Fixed Security Issues* -* `CPS-167 <https://jira.onap.org/browse/CPS-167>`_ -Update CPS dependencies as Required for Honolulu release +* `CPS-167 <https://lf-onap.atlassian.net/browse/CPS-167>`_ -Update CPS dependencies as Required for Honolulu release - Upgrade org.onap.oparent to 3.2.0 - Upgrade spring.boot to 2.3.8.RELEASE - Upgrade yangtools to 5.0.7 @@ -1754,7 +1812,7 @@ CPS code has been formally scanned during build time using NexusIQ and all Criti Test Results ------------ - * `Integration tests <https://wiki.onap.org/display/DW/CPS+Integration+Test+Cases>`_ + * `Integration tests <https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16462943/CPS+Integration+Test+Cases>`_ References ---------- @@ -1769,12 +1827,12 @@ For more information on the latest ONAP release, please see: .. _`ONAP Home Page`: https://www.onap.org -.. _`ONAP Wiki Page`: https://wiki.onap.org +.. _`ONAP Wiki Page`: https://lf-onap.atlassian.net .. _`ONAP Documentation`: https://docs.onap.org .. _`ONAP CPS Documentation`: https://docs.onap.org/projects/onap-cps .. _`ONAP Release Downloads`: https://git.onap.org Quick Links: - - `CPS project page <https://wiki.onap.org/pages/viewpage.action?pageId=71834216>`_ + - `CPS project page <https://lf-onap.atlassian.net/wiki/spaces/DW/overview>`_ - `Passing Badge information for CPS <https://bestpractices.coreinfrastructure.org/en/projects/4398>`_ diff --git a/docs/schemas/dmidataavc/avc-event-schema-1.0.0.json b/docs/schemas/dmidataavc/avc-event-schema-1.0.0.json new file mode 100644 index 0000000000..474520d142 --- /dev/null +++ b/docs/schemas/dmidataavc/avc-event-schema-1.0.0.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.events:avc-event-schema:1.0.0", + "$ref": "#/definitions/AvcEvent", + "definitions": { + "Edit": { + "additionalProperties": false, + "properties": { + "edit-id": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "target": { + "type": "string" + }, + "value": { + "type": "object", + "existingJavaType": "java.lang.Object" + } + }, + "required": [ + "edit-id", + "operation", + "target" + ] + }, + "AvcEvent": { + "description": "The payload for AVC event.", + "type": "object", + "javaType": "org.onap.cps.ncmp.events.avc1_0_0.AvcEvent", + "properties": { + "data": { + "description": "The AVC event content compliant with RFC8641 format", + "type": "object", + "additionalProperties": false, + "properties": { + "push-change-update": { + "type": "object", + "additionalProperties": false, + "properties": { + "datastore-changes": { + "type": "object", + "additionalProperties": false, + "properties": { + "ietf-yang-patch:yang-patch": { + "type": "object", + "additionalProperties": false, + "properties": { + "patch-id": { + "type": "string" + }, + "edit": { + "type": "array", + "items": { + "$ref": "#/definitions/Edit" + } + } + }, + "required": [ + "patch-id", + "edit" + ] + } + }, + "required": [ + "ietf-yang-patch:yang-patch" + ] + } + }, + "required": [ + "datastore-changes" + ] + } + }, + "required": [ + "push-change-update" + ] + } + }, + "required": [ + "data" + ], + "additionalProperties": false + } + } +}
\ No newline at end of file diff --git a/integration-test/pom.xml b/integration-test/pom.xml index ef8fdc819e..7ac9460f5e 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -113,29 +113,4 @@ <scope>test</scope> </dependency> </dependencies> - - <profiles> - <!-- Performance tests are run with maven-failsafe-plugin using a separate profile, so they will - not affect Jacoco coverage. Heap size is set here to ensure consistent test environment. --> - <profile> - <id>include-performance</id> - <properties> - <failsafeArgLine>-Xms512m -Xmx512m</failsafeArgLine> - </properties> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-failsafe-plugin</artifactId> - <configuration> - <includes> - <include>**/*PerfTest.java</include> - </includes> - </configuration> - </plugin> - </plugins> - </build> - </profile> - </profiles> - </project> diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 587cbae619..759eccd966 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -53,6 +53,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.servlet.MockMvc import org.testcontainers.spock.Testcontainers import spock.lang.Shared @@ -61,6 +62,7 @@ import spock.util.concurrent.PollingConditions import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import java.util.concurrent.BlockingQueue import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -73,6 +75,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY @EnableJpaRepositories(basePackageClasses = [DataspaceRepository]) @ComponentScan(basePackages = ['org.onap.cps']) @EntityScan('org.onap.cps.ri.models') +@ActiveProfiles('module-sync-delayed') abstract class CpsIntegrationSpecBase extends Specification { @Shared @@ -118,6 +121,9 @@ abstract class CpsIntegrationSpecBase extends Specification { ModuleSyncWatchdog moduleSyncWatchdog @Autowired + BlockingQueue<DataNode> moduleSyncWorkQueue + + @Autowired JsonObjectMapper jsonObjectMapper @Autowired @@ -244,26 +250,39 @@ abstract class CpsIntegrationSpecBase extends Specification { } def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId) - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { CmHandleState.READY == networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleId).cmHandleState }) } + def registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + } + + def registerSequenceOfCmHandlesWithoutWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles) { + def cmHandles = [] + (1..numberOfCmHandles).each { + def cmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-'+it, moduleSetTag: moduleSetTag, alternateId: NO_ALTERNATE_ID) + cmHandles.add(cmHandle) + } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: cmHandles)) + } + def deregisterCmHandle(dmiPlugin, cmHandleId) { deregisterCmHandles(dmiPlugin, [cmHandleId]) } def deregisterCmHandles(dmiPlugin, cmHandleIds) { - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } - def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) { - String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; - DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); - def jsonForUpdate = '{ "state": { "last-update-time": "%s" } }'.formatted(ISO_TIMESTAMP_FORMATTER.format(newUpdateTime)) - cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='${cmHandleId}']", jsonForUpdate, now, ContentType.JSON) + def deregisterSequenceOfCmHandles(dmiPlugin, numberOfCmHandles) { + def cmHandleIds = [] + (1..numberOfCmHandles).each { cmHandleIds.add('ch-'+it) } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } + } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy index c93a5274e6..b08d1c1548 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy @@ -20,15 +20,14 @@ package org.onap.cps.integration.base - import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest -import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper + import java.util.concurrent.TimeUnit /** diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy index 5f4ba3456c..9e51d80d9e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy @@ -134,7 +134,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2']) } - def 'Create schema set error scenario: #scenario.'() { + def 'Attempt to create schema set, error scenario: #scenario.'() { when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName' populateNewYangResourcesNameToContentMapAndAllModuleReferences(0) objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap) @@ -146,6 +146,14 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { 'schema set already exists' | FUNCTIONAL_TEST_DATASPACE_1 | BOOKSTORE_SCHEMA_SET || AlreadyDefinedException } + def 'Attempt to create duplicate schema set from modules.'() { + when: 'attempt to store duplicate schema set from modules' + objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, newYangResourcesNameToContentMap, []) + then: 'an Already Defined Exception is thrown' + thrown(AlreadyDefinedException) + } + + /* R E A D S C H E M A S E T I N F O U S E - C A S E S */ diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy index 99e80323c2..a81058fbd6 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy @@ -37,11 +37,14 @@ class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { def setup() { dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG) + dmiDispatcher1.moduleNamesPerCmHandleId['ch-2'] = ['M1', 'M3'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alt-1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'alt-2') } def cleanup() { deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') } def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { @@ -83,7 +86,7 @@ class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { "operationId": "operational-1", "datastore": "ncmp-datastore:passthrough-running", "resourceIdentifier": "my-resource-id", - "targetIds": ["ch-1"] + "targetIds": ["ch-1","alt-2"] }]}""" mvc.perform(request(POST, '/ncmp/v1/data') .queryParam('topic', 'my-topic') diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy index 10a9f15e21..ffcba025e8 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -20,7 +20,7 @@ package org.onap.cps.integration.functional.ncmp -import org.apache.kafka.common.TopicPartition +import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.common.serialization.StringDeserializer import org.onap.cps.integration.KafkaTestContainer import org.onap.cps.integration.base.CpsIntegrationSpecBase @@ -35,54 +35,69 @@ import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import spock.util.concurrent.PollingConditions import java.time.Duration -import java.time.OffsetDateTime class CmHandleCreateSpec extends CpsIntegrationSpecBase { NetworkCmProxyInventoryFacade objectUnderTest + def uniqueId = 'ch-unique-id-for-create-test' - def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) + static KafkaConsumer kafkaConsumer def setup() { objectUnderTest = networkCmProxyInventoryFacade + subscribeAndClearPreviousMessages() } - def 'CM Handle registration is successful.'() { + def cleanupSpec() { + kafkaConsumer.unsubscribe() + kafkaConsumer.close() + } + + def 'CM Handle registration.'() { given: 'DMI will return modules when requested' dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - - and: 'consumer subscribed to topic' - kafkaConsumer.subscribe(['ncmp-events']) + dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2'] when: 'a CM-handle is registered for creation' - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: uniqueId) def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'registration gives successful response' - assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] + assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(uniqueId)] and: 'CM-handle is initially in ADVISED state' - assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState + + then: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - and: 'CM-handle goes to READY state after module sync' + then: 'CM-handle goes to READY state after module sync' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState }) - and: 'the messages is polled' - def message = kafkaConsumer.poll(Duration.ofMillis(10000)) - def records = message.records(new TopicPartition('ncmp-events', 0)) + and: 'the CM-handle has expected modules' + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort() - and: 'the newest lcm event notification is received with READY state' - def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent) - assert notificationMessage.event.newValues.cmHandleState.value() == 'READY' + then: 'get the latest messages' + def consumerRecords = getLatestConsumerRecords() - and: 'the CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() + and: 'both converted messages are for the correct cm handle' + def notificationMessages = [] + for (def consumerRecord : consumerRecords) { + notificationMessages.add(jsonObjectMapper.convertJsonString(consumerRecord.value().toString(), LcmEvent)) + } + assert notificationMessages.event.cmHandleId == [ uniqueId, uniqueId ] + + and: 'the oldest event is about the update to ADVISED state' + notificationMessages[0].event.newValues.cmHandleState.value() == 'ADVISED' + + and: 'the next event is about update to READY state' + notificationMessages[1].event.newValues.cmHandleState.value() == 'READY' cleanup: 'deregister CM handle' - deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, uniqueId) } def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { @@ -92,7 +107,10 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for creation' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -117,7 +135,10 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for creation with moduleSetTag "B"' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B') - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'the CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -151,7 +172,7 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'), ] def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'registration gives expected responses' assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [ @@ -173,7 +194,11 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'CM-handles are registered for creation' def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')] def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + then: 'CM-handles go to LOCKED state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED @@ -183,6 +208,9 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M2']] dmiDispatcher1.isAvailable = true + and: 'the module sync watchdog is triggered TWICE' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'Both CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { ['ch-1', 'ch-2'].each { cmHandleId -> @@ -198,4 +226,23 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { cleanup: 'deregister CM handles' deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2']) } + + def subscribeAndClearPreviousMessages() { + kafkaConsumer = KafkaTestContainer.getConsumer('test-group', StringDeserializer.class) + kafkaConsumer.subscribe(['ncmp-events']) + kafkaConsumer.poll(Duration.ofMillis(500)) + } + + def getLatestConsumerRecords() { + def consumerRecords = [] + def retryAttempts = 10 + while (consumerRecords.size() < 2) { + retryAttempts-- + consumerRecords.addAll(kafkaConsumer.poll(Duration.ofMillis(100))) + if (retryAttempts == 0) + break + } + return consumerRecords + } + } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy index 2d1588ecf9..67011f811b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy @@ -44,7 +44,7 @@ class CmHandleUpdateSpec extends CpsIntegrationSpecBase { when: "CM-handle is registered for update with new alternate ID: $newAlternateId" def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: newAlternateId) def dmiPluginRegistrationResponse = - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] @@ -74,7 +74,7 @@ class CmHandleUpdateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for update with new alternate ID' def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'new') def dmiPluginRegistrationResponse = - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) then: 'registration gives failure response, due to alternate ID being already associated' assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createFailureResponse('ch-1', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED)] diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy index f93f58ce20..64449371fe 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy @@ -48,7 +48,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' @@ -63,6 +63,9 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'DMI will return different modules for upgrade: M1 and M3' dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState @@ -98,12 +101,15 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: "CM-handle is upgraded to moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + and: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState @@ -132,7 +138,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'CM-handle is upgraded with the same moduleSetTag' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'same') - objectUnderTest.updateDmiRegistrationAndSyncModule( + objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'CM-handle remains in READY state' @@ -157,9 +163,12 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'the CM-handle is upgraded' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag') - objectUnderTest.updateDmiRegistrationAndSyncModule( + objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy new file mode 100644 index 0000000000..e0bb437a7c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy @@ -0,0 +1,99 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog + +import java.util.concurrent.Executors + +class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase { + + ModuleSyncWatchdog objectUnderTest + + def executorService = Executors.newFixedThreadPool(2) + def SYNC_SAMPLE_SIZE = 100 + + def setup() { + objectUnderTest = moduleSyncWatchdog + registerSequenceOfCmHandlesWithoutWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, SYNC_SAMPLE_SIZE) + } + + def cleanup() { + try { + deregisterSequenceOfCmHandles(DMI1_URL, SYNC_SAMPLE_SIZE) + moduleSyncWorkQueue.clear() + } finally { + executorService.shutdownNow() + } + } + + def 'Watchdog is disabled for test.'() { + when: 'wait a while but less then the initial delay of 10 minutes' + Thread.sleep(3000) + then: 'the work queue remains empty' + assert moduleSyncWorkQueue.isEmpty() + } + + def 'Populate module sync work queue simultaneously on two parallel threads (CPS-2403).'() { + // This test failed before bug https://lf-onap.atlassian.net/browse/CPS-2403 was fixed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread at the same time' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithoutDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def 'Populate module sync work queue on two parallel threads with a slight difference in start time.'() { + // This test proved that the issue in CPS-2403 did not arise if the the queue was populated and given time to be distributed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread a little later' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def populateQueueWithoutDelay = () -> { + try { + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + + def populateQueueWithDelay = () -> { + try { + Thread.sleep(10) + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy index 1d4d19bee0..56d4bfaee4 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy @@ -68,7 +68,7 @@ class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase { 'accepted cm handle' | 'ch-1' | 'mock expects "ABC"' || 201 || 'allow' 'un-accepted cm handle' | 'ch-2' | 'mock expects "ABC"' || 409 || 'deny from mock server (dispatcher)' 'timeout' | 'ch-3' | 'mock expects "ABC"' || 409 || 'test default decision' - 'invalid authorization' | 'ch-1' | 'something else' || 500 || '401 Unauthorized from POST http://localhost:8790/policy-executor/api/v1/execute' + 'invalid authorization' | 'ch-1' | 'something else' || 409 || 'test default decision' } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index 1e1af556f1..7ce3cf5e17 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -44,6 +44,8 @@ class RestApiSpec extends CpsIntegrationSpecBase { def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { (1..3).each { @@ -65,15 +67,20 @@ class RestApiSpec extends CpsIntegrationSpecBase { ] }""".formatted(moduleName) expect: "a search for module ${moduleName} returns expected CM handles" - mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) + mvc.perform(post('/ncmp/v1/ch/id-searches'+outputAlternateId).contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) - .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); + .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandleReferences.toArray()))) + .andExpect(jsonPath('$', hasSize(expectedCmHandleReferences.size()))); where: - moduleName || expectedCmHandles - 'M1' || ['ch-1', 'ch-2', 'ch-3'] - 'M2' || ['ch-1', 'ch-2'] - 'M3' || ['ch-3'] + moduleName | outputAlternateId || expectedCmHandleReferences + 'M1' | '?outputAlternateId=false' || ['ch-1', 'ch-2', 'ch-3'] + 'M2' | '?outputAlternateId=false' || ['ch-1', 'ch-2'] + 'M3' | '?outputAlternateId=false' || ['ch-3'] + 'M1' | '?outputAlternateId=true' || ['alt-1', 'alt-2', 'alt-3'] + 'M2' | '?outputAlternateId=true' || ['alt-1', 'alt-2'] + 'M3' | '?outputAlternateId=true' || ['alt-3'] + 'M1' | '' || ['ch-1', 'ch-2', 'ch-3'] + } def 'Search for CM Handles using Cps Path Query.'() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy index f6ae27d129..fb5a0c3eb1 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy @@ -27,11 +27,13 @@ import org.onap.cps.utils.ContentType import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT class NcmpPerfTestBase extends PerfTestBase { def static NCMP_PERFORMANCE_TEST_DATASPACE = 'ncmpPerformance' - def static REGISTRY_ANCHOR = 'ncmp-registry' + def static REGISTRY_ANCHOR = NCMP_DMI_REGISTRY_ANCHOR + def static REGISTRY_PARENT = NCMP_DMI_REGISTRY_PARENT def static REGISTRY_SCHEMA_SET = 'registrySchemaSet' def static TOTAL_CM_HANDLES = 20_000 def static CM_DATA_SUBSCRIPTIONS_ANCHOR = 'cm-data-subscriptions' @@ -70,30 +72,30 @@ class NcmpPerfTestBase extends PerfTestBase { } def createRegistrySchemaSet() { - def modelAsString = readResourceDataFile('ncmp-registry/dmi-registry@2024-02-23.yang') + def modelAsString = readResourceDataFile('inventory/dmi-registry@2024-02-23.yang') cpsModuleService.createSchemaSet(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, [registry: modelAsString]) } def addRegistryData() { cpsAnchorService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, REGISTRY_ANCHOR) cpsDataService.saveData(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '{"dmi-registry": []}', now) - def innerNodeJsonTemplate = readResourceDataFile('ncmp-registry/innerNode.json') + def cmHandleJsonTemplate = readResourceDataFile('inventory/cmHandleTemplate.json') def batchSize = 100 for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) { - def data = '{ "cm-handles": [' + (1..batchSize).collect { innerNodeJsonTemplate.replace('CMHANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) + def data = '{ "cm-handles": [' + (1..batchSize).collect { cmHandleJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}' + cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, REGISTRY_PARENT, data, now, ContentType.JSON) } } def addRegistryDataWithAlternateIdAsPath() { - def innerNodeJsonTemplate = readResourceDataFile('ncmp-registry/innerCmHandleNode.json') + def cmHandleWithAlternateIdTemplate = readResourceDataFile('inventory/cmHandleWithAlternateIdTemplate.json') def batchSize = 10 for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) { def data = '{ "cm-handles": [' + (1..batchSize).collect { - innerNodeJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) + cmHandleWithAlternateIdTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) .replace('ALTERNATE_ID_AS_PATH', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, REGISTRY_PARENT, data, now, ContentType.JSON) } } @@ -117,7 +119,7 @@ class NcmpPerfTestBase extends PerfTestBase { def result = cpsDataService.getDataNodes(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/', FetchDescendantsOption.OMIT_DESCENDANTS) resourceMeter.stop() then: 'expected data exists' - assert result.xpath == ['/dmi-registry'] + assert result.xpath == [REGISTRY_PARENT] and: 'operation completes within expected time' recordAndAssertResourceUsage('NCMP pre-load test data', 15, resourceMeter.totalTimeInSeconds, diff --git a/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java index d41f752912..ff4aec4175 100644 --- a/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java +++ b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java @@ -21,6 +21,7 @@ package org.onap.cps.integration; import java.util.HashMap; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.serialization.StringDeserializer; @@ -33,11 +34,12 @@ import org.testcontainers.utility.DockerImageName; * This ensures only one instance of Kafka container across the integration tests. * Avoid unnecessary resource and time consumption. */ +@Slf4j public class KafkaTestContainer extends KafkaContainer { private static final String IMAGE_NAME_AND_VERSION = "registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1"; - private static KafkaTestContainer kafkaTestContainer; + private static volatile KafkaTestContainer kafkaTestContainer; private KafkaTestContainer() { super(DockerImageName.parse(IMAGE_NAME_AND_VERSION).asCompatibleSubstituteFor("confluentinc/cp-kafka")); @@ -51,8 +53,15 @@ public class KafkaTestContainer extends KafkaContainer { */ public static KafkaTestContainer getInstance() { if (kafkaTestContainer == null) { - kafkaTestContainer = new KafkaTestContainer(); - Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::close)); + synchronized (KafkaTestContainer.class) { + if (kafkaTestContainer == null) { + kafkaTestContainer = new KafkaTestContainer(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("Shutting down KafkaTestContainer..."); + kafkaTestContainer.stop(); + })); + } + } } return kafkaTestContainer; } @@ -63,8 +72,11 @@ public class KafkaTestContainer extends KafkaContainer { @Override public void start() { - super.start(); - System.setProperty("spring.kafka.properties.bootstrap.servers", kafkaTestContainer.getBootstrapServers()); + if (!isRunning()) { + super.start(); + System.setProperty("spring.kafka.properties.bootstrap.servers", getBootstrapServers()); + log.info("KafkaTestContainer started at {}", getBootstrapServers()); + } } @Override @@ -78,8 +90,9 @@ public class KafkaTestContainer extends KafkaContainer { configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaTestContainer.getBootstrapServers()); configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, valueDeserializer); - configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); configProps.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId); + configProps.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, Integer.MAX_VALUE); return configProps; } diff --git a/integration-test/src/test/resources/application-module-sync-delayed.yml b/integration-test/src/test/resources/application-module-sync-delayed.yml new file mode 100644 index 0000000000..7b9c6aea4f --- /dev/null +++ b/integration-test/src/test/resources/application-module-sync-delayed.yml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2024 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========================================================= + +test: + ncmp: + timers: + advised-modules-sync: + initial-delay-ms: 600000 + diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index 793acc6395..b786a3d4c5 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -179,9 +179,7 @@ ncmp: timers: advised-modules-sync: - sleep-time-ms: 1000 - locked-modules-sync: - sleep-time-ms: 1000 + sleep-time-ms: 1000000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: @@ -230,6 +228,7 @@ ncmp: hazelcast: cluster-name: cps-and-ncmp-test-caches + instance-config-name: "cps-and-ncmp-hazelcast-instance-test-config" mode: kubernetes: enabled: false diff --git a/integration-test/src/test/resources/data/ncmp-registry/innerNode.json b/integration-test/src/test/resources/data/inventory/cmHandleTemplate.json index b6c65f3763..6577f4e560 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/innerNode.json +++ b/integration-test/src/test/resources/data/inventory/cmHandleTemplate.json @@ -1,6 +1,6 @@ { - "id": "cm-CMHANDLE_ID_HERE", - "alternate-id": "alt-CMHANDLE_ID_HERE", + "id": "cm-CM_HANDLE_ID_HERE", + "alternate-id": "alt-CM_HANDLE_ID_HERE", "module-set-tag": "my-module-set-tag", "dmi-service-name": "http://ncmp-dmi-plugin-stub:8080", "dmi-data-service-name": "", @@ -21,4 +21,4 @@ } } } -}
\ No newline at end of file +} diff --git a/integration-test/src/test/resources/data/ncmp-registry/innerCmHandleNode.json b/integration-test/src/test/resources/data/inventory/cmHandleWithAlternateIdTemplate.json index 88446c4a0f..88446c4a0f 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/innerCmHandleNode.json +++ b/integration-test/src/test/resources/data/inventory/cmHandleWithAlternateIdTemplate.json diff --git a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2024-02-23.yang b/integration-test/src/test/resources/data/inventory/dmi-registry@2024-02-23.yang index d7b4ff7550..d7b4ff7550 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2024-02-23.yang +++ b/integration-test/src/test/resources/data/inventory/dmi-registry@2024-02-23.yang diff --git a/jacoco-report/pom.xml b/jacoco-report/pom.xml index 503500f529..1f43153977 100644 --- a/jacoco-report/pom.xml +++ b/jacoco-report/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/k6-tests/ncmp/common/search-base.js b/k6-tests/ncmp/common/search-base.js index 1e98e9b07b..af2caf71ec 100644 --- a/k6-tests/ncmp/common/search-base.js +++ b/k6-tests/ncmp/common/search-base.js @@ -43,7 +43,7 @@ const SEARCH_PARAMETERS_PER_SCENARIO = { "cmHandleQueryParameters": [ { "conditionName": "hasAllModules", - "conditionParameters": [{"moduleName": "ietf-yang-types"}] + "conditionParameters": [{"moduleName": "module100"}] } ] }, diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js index b52866c3fb..a2467edf6f 100644 --- a/k6-tests/ncmp/common/utils.js +++ b/k6-tests/ncmp/common/utils.js @@ -27,10 +27,10 @@ export const READ_DATA_FOR_CM_HANDLE_DELAY_MS = 300; // must have same value as export const WRITE_DATA_FOR_CM_HANDLE_DELAY_MS = 670; // must have same value as in docker-compose.yml export const CONTENT_TYPE_JSON_PARAM = {'Content-Type': 'application/json'}; export const LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE = 200; -export const LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS = 1000; +export const LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS = 100; export const LEGACY_BATCH_TOPIC_NAME = 'legacy_batch_topic'; export const KAFKA_BOOTSTRAP_SERVERS = ['localhost:9092']; -export const MODULE_SET_TAGS = ['tagA','tagB','tagC',' tagD'] +export const MODULE_SET_TAGS = ['tagA', 'tagB', 'tagC', 'tagD', 'tagE']; /** @@ -85,23 +85,23 @@ export function makeCustomSummaryReport(testResults, scenarioConfig) { const summaryCsvLines = [ '#,Test Name,Unit,Fs Requirement,Current Expectation,Actual', makeSummaryCsvLine('0', 'HTTP request failures for all tests', 'rate of failed requests', 'http_req_failed', 0, testResults, scenarioConfig), - makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 110, testResults, scenarioConfig), - makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', 80, testResults, scenarioConfig), - makeSummaryCsvLine('3a', 'CM-handle ID search with No filter', 'milliseconds', 'id_search_nofilter_duration', 4000, testResults, scenarioConfig), - makeSummaryCsvLine('3b', 'CM-handle ID search with Module filter', 'milliseconds', 'id_search_module_duration', 4000, testResults, scenarioConfig), - makeSummaryCsvLine('3c', 'CM-handle ID search with Property filter', 'milliseconds', 'id_search_property_duration', 4000, testResults, scenarioConfig), - makeSummaryCsvLine('3d', 'CM-handle ID search with Cps Path filter', 'milliseconds', 'id_search_cpspath_duration', 4000, testResults, scenarioConfig), - makeSummaryCsvLine('3e', 'CM-handle ID search with Trust Level filter', 'milliseconds', 'id_search_trustlevel_duration', 4000, testResults, scenarioConfig), - makeSummaryCsvLine('4a', 'CM-handle search with No filter', 'milliseconds', 'cm_search_nofilter_duration', 30000, testResults, scenarioConfig), - makeSummaryCsvLine('4b', 'CM-handle search with Module filter', 'milliseconds', 'cm_search_module_duration', 30000, testResults, scenarioConfig), - makeSummaryCsvLine('4c', 'CM-handle search with Property filter', 'milliseconds', 'cm_search_property_duration', 30000, testResults, scenarioConfig), - makeSummaryCsvLine('4d', 'CM-handle search with Cps Path filter', 'milliseconds', 'cm_search_cpspath_duration', 30000, testResults, scenarioConfig), - makeSummaryCsvLine('4e', 'CM-handle search with Trust Level filter', 'milliseconds', 'cm_search_trustlevel_duration', 30000, testResults, scenarioConfig), - makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', 40, testResults, scenarioConfig), + makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 70, testResults, scenarioConfig), + makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', 90, testResults, scenarioConfig), + makeSummaryCsvLine('3a', 'CM-handle ID search with No filter', 'milliseconds', 'id_search_nofilter_duration', 400, testResults, scenarioConfig), + makeSummaryCsvLine('3b', 'CM-handle ID search with Module filter', 'milliseconds', 'id_search_module_duration', 200, testResults, scenarioConfig), + makeSummaryCsvLine('3c', 'CM-handle ID search with Property filter', 'milliseconds', 'id_search_property_duration', 1300, testResults, scenarioConfig), + makeSummaryCsvLine('3d', 'CM-handle ID search with Cps Path filter', 'milliseconds', 'id_search_cpspath_duration', 1300, testResults, scenarioConfig), + makeSummaryCsvLine('3e', 'CM-handle ID search with Trust Level filter', 'milliseconds', 'id_search_trustlevel_duration', 10000, testResults, scenarioConfig), + makeSummaryCsvLine('4a', 'CM-handle search with No filter', 'milliseconds', 'cm_search_nofilter_duration', 14000, testResults, scenarioConfig), + makeSummaryCsvLine('4b', 'CM-handle search with Module filter', 'milliseconds', 'cm_search_module_duration', 16000, testResults, scenarioConfig), + makeSummaryCsvLine('4c', 'CM-handle search with Property filter', 'milliseconds', 'cm_search_property_duration', 16000, testResults, scenarioConfig), + makeSummaryCsvLine('4d', 'CM-handle search with Cps Path filter', 'milliseconds', 'cm_search_cpspath_duration', 16000, testResults, scenarioConfig), + makeSummaryCsvLine('4e', 'CM-handle search with Trust Level filter', 'milliseconds', 'cm_search_trustlevel_duration', 26000, testResults, scenarioConfig), + makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', 30, testResults, scenarioConfig), makeSummaryCsvLine('5b', 'NCMP overhead for Synchronous single CM-handle pass-through read with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_read_alt_id', 60, testResults, scenarioConfig), makeSummaryCsvLine('6a', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', 30, testResults, scenarioConfig), makeSummaryCsvLine('6b', 'NCMP overhead for Synchronous single CM-handle pass-through write with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_write_alt_id', 60, testResults, scenarioConfig), - makeSummaryCsvLine('7', 'Legacy batch read operation', 'events/second', 'legacy_batch_read_cmhandles_per_second', 1100, testResults, scenarioConfig), + makeSummaryCsvLine('7', 'Legacy batch read operation', 'events/second', 'legacy_batch_read_cmhandles_per_second', 1500, testResults, scenarioConfig), ]; return summaryCsvLines.join('\n') + '\n'; } diff --git a/k6-tests/ncmp/ncmp-kpi.js b/k6-tests/ncmp/ncmp-kpi.js index 8815511bdd..e46c547c30 100644 --- a/k6-tests/ncmp/ncmp-kpi.js +++ b/k6-tests/ncmp/ncmp-kpi.js @@ -58,8 +58,8 @@ const DURATION = '15m'; const LEGACY_BATCH_THROUGHPUT_TEST_START_TIME = '15m30s'; export const options = { - setupTimeout: '8m', - teardownTimeout: '6m', + setupTimeout: '20m', + teardownTimeout: '20m', scenarios: { passthrough_read_scenario: { executor: 'constant-vus', @@ -345,8 +345,7 @@ export function legacyBatchConsumeScenario() { let startTime = Date.now(); while (messagesConsumed < TOTAL_MESSAGES_TO_CONSUME) { - let messages = legacyBatchEventReader.consume({ limit: 1000 }); - + let messages = legacyBatchEventReader.consume({ limit: LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE }); if (messages.length > 0) { messagesConsumed += messages.length; } diff --git a/k6-tests/once-off-test/kafka/produce-avc-event.js b/k6-tests/once-off-test/kafka/produce-avc-event.js new file mode 100644 index 0000000000..db222f6a4a --- /dev/null +++ b/k6-tests/once-off-test/kafka/produce-avc-event.js @@ -0,0 +1,105 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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 { crypto } from 'k6/experimental/webcrypto'; +import { check } from 'k6'; +import { Writer, SchemaRegistry, SCHEMA_TYPE_STRING } from 'k6/x/kafka'; + +const testEventPayload = JSON.stringify(JSON.parse(open('../../resources/sampleAvcInputEvent.json'))); +const schemaRegistry = new SchemaRegistry(); +const kafkaProducer = new Writer({ + brokers: ['localhost:9092'], + topic: 'dmi-cm-events', + autoCreateTopic: true, + batchSize: 5000, + compression: 'gzip', + requestTimeout: 30000 +}); + +const TOTAL_MESSAGES = 100000; +const VIRTUAL_USERS = 1000; + +export const options = { + setupTimeout: '1m', + teardownTimeout: '1m', + scenarios: { + produceKafkaMessages: { + executor: 'shared-iterations', + exec: 'sendKafkaMessages', + vus: VIRTUAL_USERS, + iterations: TOTAL_MESSAGES, + maxDuration: '10m', + } + } +}; + +const getRandomNetworkElement = () => { + const networkElementIds = Array.from({ length: 10 }, (_, i) => `neType-${i + 1}`); + return networkElementIds[Math.floor(Math.random() * networkElementIds.length)]; +}; + +function getCloudEventHeaders() { + return { + ce_type: 'org.onap.cps.ncmp.events.avc1_0_0.AvcEvent', + ce_source: 'DMI', + ce_destination: 'dmi-cm-events', + ce_specversion: '1.0', + ce_time: new Date().toISOString(), + ce_id: crypto.randomUUID(), + ce_dataschema: 'urn:cps:org.onap.cps.ncmp.events.avc1_0_0.AvcEvent:1.0.0', + ce_correlationid: crypto.randomUUID() + }; +} + +export function sendKafkaMessages() { + const cloudEventHeaders = getCloudEventHeaders(); + const networkElementId = getRandomNetworkElement(); + + const avcCloudEvent = { + key: schemaRegistry.serialize({ + data: networkElementId, + schemaType: SCHEMA_TYPE_STRING, + }), + value: schemaRegistry.serialize({ + data: testEventPayload, + schemaType: SCHEMA_TYPE_STRING + }), + headers: cloudEventHeaders + }; + + try { + kafkaProducer.produce({ messages: [avcCloudEvent] }); + + const isMessageSent = check(kafkaProducer, { + 'Message sent successfully': (producer) => producer != null, + }); + + if (!isMessageSent) { + console.error('Failed to send message:', avcCloudEvent); + } + + } catch (error) { + console.error('Error during message production:', error, avcCloudEvent); + } +} + +export function teardown() { + kafkaProducer.close(); +} diff --git a/k6-tests/resources/sampleAvcInputEvent.json b/k6-tests/resources/sampleAvcInputEvent.json new file mode 100644 index 0000000000..4c9cd721df --- /dev/null +++ b/k6-tests/resources/sampleAvcInputEvent.json @@ -0,0 +1,38 @@ +{ + "data": { + "push-change-update": { + "datastore-changes": { + "ietf-yang-patch:yang-patch": { + "patch-id": "34534ffd98", + "edit": [ + { + "edit-id": "ded43434-1", + "operation": "replace", + "target": "ancestor:ancestor/parent[@id='parent1']/child[@id='child1']/grandchild[@id='grandchild1']/relation[@id='relation1']", + "value": { + "attributes": [] + } + }, + { + "edit-id": "ded43434-2", + "operation": "create", + "target": "ancestor:ancestor/parent[@id='parent1']/child[@id='child1']/grandchild[@id='grandchild1']/relation[@id='relation1']", + "value": { + "attributes": [ + { + "isHoAllowed": false + } + ] + } + }, + { + "edit-id": "ded43434-3", + "operation": "delete", + "target": "ancestor:ancestor/parent[@id='parent1']/child[@id='child1']/grandchild[@id='grandchild1']/relation[@id='relation1']" + } + ] + } + } + } + } +}
\ No newline at end of file diff --git a/k6-tests/setup.sh b/k6-tests/setup.sh index 346b9c0690..a4508e180d 100755 --- a/k6-tests/setup.sh +++ b/k6-tests/setup.sh @@ -15,10 +15,10 @@ # limitations under the License. # -docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub up -d +docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub up --quiet-pull -d echo "Waiting for CPS to start..." -READY_MESSAGE="Processing module sync fetched 0 advised cm handles from DB" +READY_MESSAGE="Inventory Model updated successfully" # Get the container IDs of the cps-and-ncmp replicas CONTAINER_IDS=$(docker ps --filter "name=cps-and-ncmp" --format "{{.ID}}") @@ -28,3 +28,8 @@ for CONTAINER_ID in $CONTAINER_IDS; do echo "Checking logs for container: $CONTAINER_ID" docker logs "$CONTAINER_ID" -f | grep -m 1 "$READY_MESSAGE" >/dev/null && echo "CPS is ready in container: $CONTAINER_ID" || true done + +# Output build information including git commit info +echo "Build information:" +curl http://localhost:8883/actuator/info +echo diff --git a/policy-executor-stub/pom.xml b/policy-executor-stub/pom.xml index 35ff835505..3618d7816c 100644 --- a/policy-executor-stub/pom.xml +++ b/policy-executor-stub/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java index cdd26c96e9..88073c0a0f 100644 --- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java @@ -41,9 +41,11 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j public class PolicyExecutorStubController implements PolicyExecutorApi { + private final Sleeper sleeper; private final ObjectMapper objectMapper; private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})"); private int decisionCounter = 0; + private static int slowResponseTimeInSeconds = 40; @Override public ResponseEntity<PolicyExecutionResponse> executePolicyAction( @@ -85,7 +87,14 @@ public class PolicyExecutorStubController implements PolicyExecutorApi { final String decisionId = String.valueOf(++decisionCounter); final String decision; final String message; - + if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("slow")) { + try { + sleeper.haveALittleRest(slowResponseTimeInSeconds); + } catch (final InterruptedException e) { + log.trace("Sleep interrupted, re-interrupting the thread"); + Thread.currentThread().interrupt(); // Re-interrupt the thread + } + } if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { decision = "allow"; message = "All good"; diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java new file mode 100644 index 0000000000..789201ffce --- /dev/null +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.policyexecutor.stub.controller; + +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Service; + +/** + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get too 100% code coverage. + */ +@Service +public class Sleeper { + public void haveALittleRest(final int timeInSeconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(timeInSeconds); + } +} diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy index 064e0234a3..44460daa7e 100644 --- a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy @@ -25,6 +25,7 @@ import org.onap.cps.policyexecutor.stub.model.NcmpDelete import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse import org.onap.cps.policyexecutor.stub.model.Request +import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus @@ -43,8 +44,15 @@ class PolicyExecutorStubControllerSpec extends Specification { @Autowired ObjectMapper objectMapper + @SpringBean + Sleeper sleeper = Spy() + def url = '/policy-executor/api/v1/some-action' + def setup() { + PolicyExecutorStubController.slowResponseTimeInSeconds = 1 + } + def 'Execute policy action.'() { given: 'a policy execution request with target: #targetIdentifier' def requestBody = createRequestBody(targetIdentifier) @@ -66,6 +74,7 @@ class PolicyExecutorStubControllerSpec extends Specification { targetIdentifier || expectedDecsisonId | expectedDecision | expectedMessage 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" 'fdn with cps-is-great' || '2' | 'allow' | 'All good' + 'slow' || '3' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" } def 'Execute policy action with a HTTP error code.'() { @@ -118,6 +127,19 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.BAD_REQUEST.value() } + def 'Execute policy action with interrupted exception during slow response.'() { + given: 'a policy execution request with target: "slow"' + def requestBody = createRequestBody('slow') + sleeper.haveALittleRest(_) >> { throw new InterruptedException() } + when: 'request is posted' + mockMvc.perform(post(url) + .header('Authorization','some string') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + then: 'response status is Bad Request' + noExceptionThrown() + } + def 'Execute policy action with missing or invalid attributes.'() { given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier' def requestBody = createRequestBody(decisionType, schema, targetIdentifier) @@ -23,16 +23,11 @@ <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.onap.oparent</groupId>
- <artifactId>oparent</artifactId>
- <version>3.2.0</version>
- </parent>
+ <modelVersion>4.0.0</modelVersion>
<groupId>org.onap.cps</groupId>
<artifactId>cps-aggregator</artifactId>
- <version>3.5.3-SNAPSHOT</version>
+ <version>3.5.5-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cps</name>
@@ -46,6 +41,10 @@ <properties>
<maven.deploy.skip>false</maven.deploy.skip>
<maven.install.skip>false</maven.install.skip>
+ <onap.nexus.url>https://nexus.onap.org</onap.nexus.url>
+ <nexusproxy>https://nexus.onap.org</nexusproxy>
+ <releaseNexusPath>/content/repositories/releases/</releaseNexusPath>
+ <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath>
</properties>
<modules>
@@ -69,6 +68,19 @@ <module>policy-executor-stub</module>
</modules>
+ <distributionManagement>
+ <repository>
+ <id>ecomp-releases</id>
+ <name>ECOMP Release Repository</name>
+ <url>${onap.nexus.url}${releaseNexusPath}</url>
+ </repository>
+ <snapshotRepository>
+ <id>ecomp-snapshots</id>
+ <name>ECOMP Snapshot Repository</name>
+ <url>${onap.nexus.url}${snapshotNexusPath}</url>
+ </snapshotRepository>
+ </distributionManagement>
+
<build>
<plugins>
<plugin>
diff --git a/releases/3.5.3-container.yaml b/releases/3.5.3-container.yaml new file mode 100644 index 0000000000..de99979bb6 --- /dev/null +++ b/releases/3.5.3-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.5.3 +project: cps +log_dir: cps-maven-docker-stage-master/946/ +ref: ef70022bddcb18a17c4afe2332cafa78621ab8b5 +containers: + - name: 'cps-and-ncmp' + version: '3.5.3-20241004T144917Z' diff --git a/releases/3.5.3.yaml b/releases/3.5.3.yaml new file mode 100644 index 0000000000..db6944c3c8 --- /dev/null +++ b/releases/3.5.3.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/954/ +project: cps +version: 3.5.3 diff --git a/releases/3.5.4-container.yaml b/releases/3.5.4-container.yaml new file mode 100644 index 0000000000..7da8f7b56c --- /dev/null +++ b/releases/3.5.4-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.5.4 +project: cps +log_dir: cps-maven-docker-stage-master/947/ +ref: 557404338f8867f8253f82526b43313e669550e0 +containers: + - name: 'cps-and-ncmp' + version: '3.5.4-20241017T151242Z' diff --git a/releases/3.5.4.yaml b/releases/3.5.4.yaml new file mode 100644 index 0000000000..fc987985b5 --- /dev/null +++ b/releases/3.5.4.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/955/ +project: cps +version: 3.5.4 diff --git a/spotbugs/pom.xml b/spotbugs/pom.xml index 1433bd3a39..f2b4175f58 100644 --- a/spotbugs/pom.xml +++ b/spotbugs/pom.xml @@ -25,10 +25,10 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>spotbugs</artifactId> - <version>3.5.3-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <properties> - <nexusproxy>https://nexus.onap.org</nexusproxy> + <onap.nexus.url>https://nexus.onap.org</onap.nexus.url> <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> </properties> @@ -39,7 +39,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.8.2</version> + <version>3.1.2</version> </plugin> </plugins> </pluginManagement> @@ -49,12 +49,12 @@ <repository> <id>ecomp-releases</id> <name>ECOMP Release Repository</name> - <url>${nexusproxy}${releaseNexusPath}</url> + <url>${onap.nexus.url}${releaseNexusPath}</url> </repository> <snapshotRepository> <id>ecomp-snapshots</id> <name>ECOMP Snapshot Repository</name> - <url>${nexusproxy}${snapshotNexusPath}</url> + <url>${onap.nexus.url}${snapshotNexusPath}</url> </snapshotRepository> </distributionManagement> </project>
\ No newline at end of file diff --git a/version.properties b/version.properties index 5b9387554a..10de88bf6b 100644 --- a/version.properties +++ b/version.properties @@ -22,7 +22,7 @@ major=3 minor=5 -patch=3 +patch=5 base_version=${major}.${minor}.${patch} |