Merge "Watchdog-process that changes CM Handles state"
authorJoseph Keenan <joseph.keenan@est.tech>
Tue, 3 May 2022 08:53:36 +0000 (08:53 +0000)
committerGerrit Code Review <gerrit@onap.org>
Tue, 3 May 2022 08:53:36 +0000 (08:53 +0000)
16 files changed:
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java
cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java [new file with mode: 0644]
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy [moved from cps-service/src/test/groovy/org/onap/cps/utils/YangTextSchemaSourceSetSpec.groovy with 88% similarity]
docs/architecture.rst
docs/cps-path.rst
docs/deployment.rst

index 847a1d1..daf4dd7 100644 (file)
@@ -220,6 +220,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         sessionManager.closeSession(sessionId);
     }
 
+    @Override
+    public void lockAnchor(final String sessionId, final String dataspaceName,
+                            final String anchorName, final Long timeoutInMilliseconds) {
+        sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
+    }
+
     private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
         final CpsPathQuery cpsPathQuery) {
         final Set<String> ancestorXpath = new HashSet<>();
index eb535ec..e278688 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.spi.utils;
 
-import java.util.HashMap;
-import java.util.Map;
+import com.google.common.util.concurrent.TimeLimiter;
+import com.google.common.util.concurrent.UncheckedExecutionException;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
 import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
 import org.hibernate.Session;
-import org.hibernate.SessionException;
 import org.hibernate.SessionFactory;
 import org.hibernate.cfg.Configuration;
 import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.entities.YangResourceEntity;
+import org.onap.cps.spi.exceptions.SessionManagerException;
+import org.onap.cps.spi.exceptions.SessionTimeoutException;
+import org.onap.cps.spi.repository.AnchorRepository;
+import org.onap.cps.spi.repository.DataspaceRepository;
 import org.springframework.stereotype.Component;
 
+@RequiredArgsConstructor
+@Slf4j
 @Component
 public class SessionManager {
 
+    private final TimeLimiterProvider timeLimiterProvider;
+    private final DataspaceRepository dataspaceRepository;
+    private final AnchorRepository anchorRepository;
     private static SessionFactory sessionFactory;
-    private static Map<String, Session> sessionMap = new HashMap<>();
+    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
 
     private synchronized void buildSessionFactory() {
         if (sessionFactory == null) {
@@ -67,20 +85,81 @@ public class SessionManager {
 
     /**
      * Close session.
+     * Locks will be released and changes will be committed.
      *
      * @param sessionId session ID
      */
     public void closeSession(final String sessionId) {
         try {
-            final Session currentSession = sessionMap.get(sessionId);
-            currentSession.getTransaction().commit();
-            currentSession.close();
-        } catch (final NullPointerException e) {
-            throw new SessionException(String.format("Session with session ID %s does not exist", sessionId));
+            final Session session = getSession(sessionId);
+            session.getTransaction().commit();
+            session.close();
         } catch (final HibernateException e) {
-            throw new SessionException(String.format("Unable to close session with session ID %s", sessionId));
+            throw new SessionManagerException("Cannot close session",
+                String.format("Unable to close session with session ID '%s'", sessionId), e);
+        } finally {
+            sessionMap.remove(sessionId);
         }
-        sessionMap.remove(sessionId);
     }
 
-}
\ No newline at end of file
+    /**
+     * Lock Anchor.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionId session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    @SneakyThrows
+    public void lockAnchor(final String sessionId, final String dataspaceName,
+                           final String anchorName, final Long timeoutInMilliseconds) {
+        final ExecutorService executorService = Executors.newSingleThreadExecutor();
+        final TimeLimiter timeLimiter = timeLimiterProvider.getTimeLimiter(executorService);
+
+        try {
+            timeLimiter.callWithTimeout(() -> {
+                applyPessimisticWriteLockOnAnchor(sessionId, dataspaceName, anchorName);
+                return null;
+            }, timeoutInMilliseconds, TimeUnit.MILLISECONDS);
+        } catch (final TimeoutException e) {
+            throw new SessionTimeoutException(
+                    "Timeout: Anchor locking failed",
+                    "The error could be caused by another session holding a lock on the specified table. "
+                            + "Retrying the sending the request could be required.", e);
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new SessionManagerException("Operation interrupted", "This thread was interrupted.", e);
+        } catch (final ExecutionException | UncheckedExecutionException e) {
+            if (e.getCause() != null) {
+                throw e.getCause();
+            }
+            throw new SessionManagerException(
+                    "Operation Aborted",
+                    "The transaction request was aborted. "
+                            + "Retrying and checking all details are correct could be required", e);
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    private void applyPessimisticWriteLockOnAnchor(final String sessionId, final String dataspaceName,
+                                                   final String anchorName) {
+        final Session session = getSession(sessionId);
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+        final int anchorId = anchorEntity.getId();
+        log.debug("Attempting to lock anchor {} for session {}", anchorName, sessionId);
+        session.get(AnchorEntity.class, anchorId, LockMode.PESSIMISTIC_WRITE);
+        log.info("Anchor {} successfully locked", anchorName);
+    }
+
+    private Session getSession(final String sessionId) {
+        final Session session = sessionMap.get(sessionId);
+        if (session == null) {
+            throw new SessionManagerException("Session not found",
+                String.format("Session with ID %s does not exist", sessionId));
+        }
+        return session;
+    }
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java
new file mode 100644 (file)
index 0000000..2bd7ac3
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.utils;
+
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+import com.google.common.util.concurrent.TimeLimiter;
+import java.util.concurrent.ExecutorService;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TimeLimiterProvider {
+    public TimeLimiter getTimeLimiter(final ExecutorService executorService) {
+        return SimpleTimeLimiter.create(executorService);
+    }
+}
index 52f2309..b37f471 100644 (file)
@@ -129,4 +129,11 @@ class CpsDataPersistenceServiceSpec extends Specification {
         then: 'the session manager method to close session is invoked with parameter'
             1 * mockSessionManager.closeSession(someSessionId)
     }
+
+    def 'Lock anchor.'(){
+        when: 'lock anchor method is called with anchor entity details'
+            objectUnderTest.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
+        then: 'the session manager method to lock anchor is invoked with same parameters'
+            1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
+    }
 }
\ No newline at end of file
index c46092f..9b58c8b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.spi.utils
 
-import org.hibernate.SessionException
+import org.onap.cps.spi.exceptions.SessionManagerException
 import org.onap.cps.spi.impl.CpsPersistenceSpecBase
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.jdbc.Sql
 
 class SessionManagerIntegrationSpec extends CpsPersistenceSpecBase{
 
-    def objectUnderTest = new SessionManager();
+    final static String SET_DATA = '/data/anchor.sql'
 
-    def 'start session'() {
-        when: 'start session'
-            def result = objectUnderTest.startSession()
-        then: 'session ID is returned'
-            assert result instanceof String
-            objectUnderTest.closeSession(result)
+    @Autowired
+    SessionManager objectUnderTest
+
+    def sessionId
+    def shortTimeoutForTesting = 200L
+
+    def setup(){
+        sessionId = objectUnderTest.startSession()
     }
 
-    def 'close session'(){
-        given: 'session Id from calling the start session method'
-            def sessionId = objectUnderTest.startSession()
-        when: 'close session method is called'
-            objectUnderTest.closeSession(sessionId)
+    def cleanup(){
+        objectUnderTest.closeSession(sessionId)
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Lock anchor.'(){
+        when: 'session tries to acquire anchor lock by passing anchor entity details'
+            objectUnderTest.lockAnchor(sessionId, DATASPACE_NAME, ANCHOR_NAME1, shortTimeoutForTesting)
         then: 'no exception is thrown'
             noExceptionThrown()
     }
 
-    def 'close session that does not exist' (){
-        given: 'session Id that does not exist'
-            def unknownSessionId = 'unknown session id'
-        when: 'close session method is called'
-            objectUnderTest.closeSession(unknownSessionId)
-        then: 'a session exception is thrown'
-            def thrown = thrown(SessionException)
-            assert thrown.message.contains(unknownSessionId)
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Attempt to lock anchor when another session is holding the lock.'(){
+        given: 'another session that holds an anchor lock'
+            def otherSessionId = objectUnderTest.startSession()
+            objectUnderTest.lockAnchor(otherSessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
+        when: 'a session tries to acquire the same anchor lock'
+            objectUnderTest.lockAnchor(sessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
+        then: 'a session manager exception is thrown specifying operation reached timeout'
+            def thrown = thrown(SessionManagerException)
+            thrown.message.contains('Timeout')
+        then: 'when the other session holding the lock is closed, lock can finally be acquired'
+            objectUnderTest.closeSession(otherSessionId)
+            objectUnderTest.lockAnchor(sessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
     }
+
 }
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy
new file mode 100644 (file)
index 0000000..a2df06e
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.utils
+
+import com.google.common.util.concurrent.TimeLimiter
+import org.hibernate.HibernateException
+import org.hibernate.Transaction
+import org.onap.cps.spi.entities.AnchorEntity
+import org.onap.cps.spi.exceptions.SessionManagerException
+import org.onap.cps.spi.repository.AnchorRepository
+import org.onap.cps.spi.repository.DataspaceRepository
+import org.testcontainers.shaded.com.google.common.util.concurrent.UncheckedExecutionException
+import spock.lang.Specification
+import org.hibernate.Session
+
+import java.util.concurrent.ExecutionException
+
+class SessionManagerSpec extends Specification {
+
+    def spiedTimeLimiterProvider = Spy(TimeLimiterProvider)
+    def mockDataspaceRepository = Mock(DataspaceRepository)
+    def mockAnchorRepository = Mock(AnchorRepository)
+    def mockSession = Mock(Session)
+
+    def objectUnderTest = new SessionManager(spiedTimeLimiterProvider, mockDataspaceRepository, mockAnchorRepository)
+
+    def 'Lock anchor entity with #exceptionDuringTest exception.'(){
+        given: 'a dummy session'
+            objectUnderTest.sessionMap.put('dummySession', mockSession)
+        and: 'the anchor name can be resolved'
+            def mockAnchorEntity = Mock(AnchorEntity)
+            mockAnchorEntity.getId() > 456
+            mockAnchorRepository.getByDataspaceAndName(_, _) >> mockAnchorEntity
+        and: 'timeLimiter throws an #exceptionDuringTest exception'
+            def mockTimeLimiter = Mock(TimeLimiter)
+            spiedTimeLimiterProvider.getTimeLimiter(_) >> mockTimeLimiter
+            mockTimeLimiter.callWithTimeout(*_) >> { throw exceptionDuringTest }
+        when: 'session tries to acquire anchor lock'
+            objectUnderTest.lockAnchor('dummySession', 'some-dataspace','some-anchor', 123L)
+        then: 'a session manager exception is thrown with the expected detail'
+            def thrown = thrown(SessionManagerException)
+            thrown.details.contains(expectedExceptionDetail)
+        where:
+            exceptionDuringTest               || expectedExceptionDetail
+            new InterruptedException()        || 'interrupted'
+            new ExecutionException()          || 'aborted'
+    }
+
+    def 'Close session that does not exist.'() {
+        when: 'attempt to close session that does not exist'
+            objectUnderTest.closeSession('unknown session id')
+        then: 'a session manager exception is thrown with the unknown id in the details'
+            def thrown = thrown(SessionManagerException)
+            assert thrown.details.contains('unknown session id')
+    }
+
+    def 'Hibernate exception while closing session.'() {
+        given: 'a test session with a transaction'
+            objectUnderTest.sessionMap.put('testSessionId', mockSession)
+            mockSession.getTransaction() >> Mock(Transaction)
+        and: 'an hibernate exception when closing that session'
+            def hibernateException = new HibernateException('test')
+            mockSession.close() >> { throw hibernateException }
+        when: 'attempt to close session'
+            objectUnderTest.closeSession('testSessionId')
+        then: 'a session manager exception is thrown with the session id in the details'
+            def thrown = thrown(SessionManagerException)
+            assert thrown.details.contains('testSessionId')
+        and: 'the original exception as cause'
+            assert thrown.cause == hibernateException
+    }
+
+    def 'Attempt to lock anchor entity with session Id that does not exists'(){
+        when: 'attempt to acquire anchor lock with session that does not exists'
+            objectUnderTest.lockAnchor('unknown session id','','',123L)
+        then: 'a session manager exception is thrown with the unknown id in the details'
+            def thrown = thrown(SessionManagerException)
+            thrown.details.contains('unknown session id')
+    }
+
+}
index 35caf95..93c96ec 100644 (file)
@@ -189,4 +189,26 @@ public interface CpsDataService {
      *
      */
     void closeSession(String sessionId);
+
+    /**
+     * Lock anchor with default timeout.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName);
+
+    /**
+     * Lock anchor with custom timeout.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds);
+
 }
index b95ad05..2f1067a 100755 (executable)
@@ -48,6 +48,7 @@ import org.springframework.stereotype.Service;
 public class CpsDataServiceImpl implements CpsDataService {
 
     private static final String ROOT_NODE_XPATH = "/";
+    private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
 
     private final CpsDataPersistenceService cpsDataPersistenceService;
     private final CpsAdminService cpsAdminService;
@@ -125,6 +126,17 @@ public class CpsDataServiceImpl implements CpsDataService {
         cpsDataPersistenceService.closeSession(sessionId);
     }
 
+    @Override
+    public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
+        lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
+    }
+
+    @Override
+    public void lockAnchor(final String sessionID, final String dataspaceName,
+                           final String anchorName, final Long timeoutInMilliseconds) {
+        cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
+    }
+
     @Override
     public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
index 06da3ff..fd660e6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation.
+ *  Copyright (C) 2020-2022 Nordix Foundation.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  * ================================================================================
@@ -162,4 +162,16 @@ public interface CpsDataPersistenceService {
      * @param sessionId session ID
      */
     void closeSession(String sessionId);
+
+    /**
+     * Lock anchor.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds);
+
 }
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java
new file mode 100644 (file)
index 0000000..4000bfc
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+
+public class SessionManagerException extends CpsException {
+
+    private static final long serialVersionUID = 7957090904519019500L;
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     * @param cause   the cause of the exception
+     */
+    public SessionManagerException(final String message, final String details, final Throwable cause) {
+        super(message, details, cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public SessionManagerException(final String message, final String details) {
+        super(message, details);
+    }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java
new file mode 100644 (file)
index 0000000..92b4aa7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+@SuppressWarnings("squid:S110")  // Team agreed to accept 6 levels of inheritance for CPS Exceptions
+public class SessionTimeoutException extends SessionManagerException {
+
+    private static final long serialVersionUID = -8809577494038691360L;
+
+    public SessionTimeoutException(final String message, final String details, final Throwable cause) {
+        super(message, details, cause);
+    }
+}
index faeba8d..8b9d545 100644 (file)
@@ -430,4 +430,21 @@ class CpsDataServiceImplSpec extends Specification {
         then: 'the persistence service method to close session is invoked'
             1 * mockCpsDataPersistenceService.closeSession(sessionId)
     }
+
+    def 'lock anchor with no timeout parameter'(){
+        when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
+            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
+        then: 'the persistence service method to lock anchor is invoked with default timeout'
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 300L)
+    }
+
+    def 'lock anchor with timeout parameter'(){
+        when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
+            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 250L)
+        then: 'the persistence service method to lock anchor is invoked with the given timeout'
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 250L)
+    }
 }
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020-2021 Pantheon.tech
- *  Modifications Copyright (C) 2020-2021 Nordix Foundation
+ *  Modifications Copyright (C) 2020-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.utils
+package org.onap.cps.yang
+
 
 import org.onap.cps.TestUtils
 import org.onap.cps.spi.exceptions.ModelValidationException
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.opendaylight.yangtools.yang.common.Revision
 import spock.lang.Specification
 
-class YangTextSchemaSourceSetSpec extends Specification {
+class YangTextSchemaSourceSetBuilderSpec extends Specification {
 
     def 'Building a valid YangTextSchemaSourceSet using #filenameCase filename.'() {
         given: 'a yang model (file)'
             def yangResourceNameToContent = [filename: TestUtils.getResourceFileContent('bookstore.yang')]
         when: 'the content is parsed'
             def result = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
-        then: 'the result contains 1 module of the correct name and revision'
+        then: 'it can be validated successfully'
+            YangTextSchemaSourceSetBuilder.validate(yangResourceNameToContent)
+        and: 'the result contains 1 module of the correct name and revision'
             result.modules.size() == 1
             def optionalModule = result.findModule('stores', Revision.of('2020-09-15'))
             optionalModule.isPresent()
index 26a8c63..acde1b1 100644 (file)
@@ -61,9 +61,9 @@ Configuration Persistence Service provides the following interfaces.
    * - CPS-E-04
      - Change Notification
      - - Kafka is used as the event messaging system
-       - running instance is supplied independently from ONAP DMaaP component or any Kafka instance deployed from ONAP
+       - running instance is supplied independently from any Kafka instance deployed from ONAP
        - published events contain Timestamp, Dataspace, Schema set, Anchor and JSON Data Payload
-     - DMaaP
+     - Kafka
    * - CPS-E-05
      - xNF Data Access
      - - read xNF data
index e8a75d9..fba21f3 100644 (file)
@@ -234,12 +234,15 @@ leaf-conditions
   - ``//categories[@name="Kids"]``
   - ``//categories[@name='Kids']``
   - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]``
-
+  - ``//categories[@code=1]``
 **Limitations**
   - Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s).
   - Multiple attributes can only be combined using ``and``. ``or`` and bracketing is not supported.
   - Only leaves can be used, leaf-list are not supported.
   - Only string and integer values are supported, boolean and float values are not supported.
+  - The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type
+    Integer so the cps query will not work if the value is passed as string.
+    eg: ``//categories[@code="1"]`` or ``//categories[@code='1']`` will not work because the key attribute code is treated a string.
 
 **Notes**
   - For performance reasons it does not make sense to query using key leaf as attribute. If the key value is known it is better to execute a get request with the complete xpath.
index 46160c4..06e1ddc 100644 (file)
@@ -203,26 +203,29 @@ Any spring supported property can be configured by providing in ``config.additio
 | logging.level                         | Logging level set in cps-core                                                                           | info                          |
 |                                       |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.eventPublisher.                | Kafka hostname and port                                                                                 | ``message-router-kafka:9092`` |
+| config.useStrimziKafka                | If targeting a custom kafka cluster, ie useStrimziKakfa: false, the config.eventPublisher.spring.kafka  | true                          |
+|                                       | values must be set.                                                                                     |                               |
++---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
+| config.eventPublisher.                | Kafka hostname and port                                                                                 | ``<kafka-bootstrap>:9092``    |
 | spring.kafka.bootstrap-servers        |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
 | config.eventPublisher.                | Kafka consumer client id                                                                                | ``cps-core``                  |
 | spring.kafka.consumer.client-id       |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security protocol.                                                                                | ``PLAINTEXT``                 |
+| config.eventPublisher.                | Kafka security protocol.                                                                                | ``SASL_PLAINTEXT``            |
 | spring.kafka.security.protocol        | Some possible values are:                                                                               |                               |
 |                                       |                                                                                                         |                               |
 |                                       | * ``PLAINTEXT``                                                                                         |                               |
 |                                       | * ``SASL_PLAINTEXT``, for authentication                                                                |                               |
 |                                       | * ``SASL_SSL``, for authentication and encryption                                                       |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL mechanism. Required for SASL_PLAINTEXT and SASL_SSL protocols.                      | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL mechanism. Required for SASL_PLAINTEXT and SASL_SSL protocols.                      | Not defined                   |
 | spring.kafka.properties.              | Some possible values are:                                                                               |                               |
 | sasl.mechanism                        |                                                                                                         |                               |
 |                                       | * ``PLAIN``, for PLAINTEXT                                                                              |                               |
 |                                       | * ``SCRAM-SHA-512``, for SSL                                                                            |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL JAAS configuration. Required for SASL_PLAINTEXT and SASL_SSL protocols.             | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL JAAS configuration. Required for SASL_PLAINTEXT and SASL_SSL protocols.             | Not defined                   |
 | spring.kafka.properties.              | Some possible values are:                                                                               |                               |
 | sasl.jaas.config                      |                                                                                                         |                               |
 |                                       | * ``org.apache.kafka.common.security.plain.PlainLoginModule required username="..." password="...";``,  |                               |
@@ -230,18 +233,18 @@ Any spring supported property can be configured by providing in ``config.additio
 |                                       | * ``org.apache.kafka.common.security.scram.ScramLoginModule required username="..." password="...";``,  |                               |
 |                                       |   for SSL                                                                                               |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL SSL store type. Required for SASL_SSL protocol.                                     | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL SSL store type. Required for SASL_SSL protocol.                                     | Not defined                   |
 | spring.kafka.ssl.trust-store-type     | Some possible values are:                                                                               |                               |
 |                                       |                                                                                                         |                               |
 |                                       | * ``JKS``                                                                                               |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL SSL store file location. Required for SASL_SSL protocol.                            | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL SSL store file location. Required for SASL_SSL protocol.                            | Not defined                   |
 | spring.kafka.ssl.trust-store-location |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL SSL store password. Required for SASL_SSL protocol.                                 | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL SSL store password. Required for SASL_SSL protocol.                                 | Not defined                   |
 | spring.kafka.ssl.trust-store-password |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
-| config.publisher.                     | Kafka security SASL SSL broker hostname identification verification. Required for SASL_SSL protocol.    | Not defined                   |
+| config.eventPublisher.                | Kafka security SASL SSL broker hostname identification verification. Required for SASL_SSL protocol.    | Not defined                   |
 | spring.kafka.properties.              | Possible value is:                                                                                      |                               |
 | ssl.endpoint.identification.algorithm |                                                                                                         |                               |
 |                                       | * ``""``, empty string to disable                                                                       |                               |