From: Joseph Keenan Date: Tue, 3 May 2022 08:53:36 +0000 (+0000) Subject: Merge "Watchdog-process that changes CM Handles state" X-Git-Tag: 3.1.0~133 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=cps.git;a=commitdiff_plain;h=a0b8fb72f9ab872ec554b812db10758592b5a294;hp=6369af0f6cb53619daa253d5da0f360f2bf67fc3 Merge "Watchdog-process that changes CM Handles state" --- diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 847a1d129..daf4dd757 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -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 processAncestorXpath(final List fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java index eb535ecc3..e2786887a 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java @@ -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. @@ -20,25 +20,43 @@ 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 sessionMap = new HashMap<>(); + private static ConcurrentHashMap 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 index 000000000..2bd7ac376 --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java @@ -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); + } +} diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index 52f2309cc..b37f471e7 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -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 diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy index c46092f07..9b58c8bc3 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy @@ -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. @@ -20,37 +20,50 @@ 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 index 000000000..a2df06ef0 --- /dev/null +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy @@ -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') + } + +} 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 35caf9515..93c96ec65 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 @@ -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); + } 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 b95ad051e..2f1067aaf 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -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) { diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 06da3ffc8..fd660e675 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -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 index 000000000..4000bfc51 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java @@ -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 index 000000000..92b4aa7a8 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java @@ -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); + } +} 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 faeba8d51..8b9d54529 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 @@ -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) + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangTextSchemaSourceSetSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy similarity index 88% rename from cps-service/src/test/groovy/org/onap/cps/utils/YangTextSchemaSourceSetSpec.groovy rename to cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy index b6250612e..236221aca 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangTextSchemaSourceSetSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy @@ -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"); @@ -20,22 +20,24 @@ * ============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() diff --git a/docs/architecture.rst b/docs/architecture.rst index 26a8c63b5..acde1b1ca 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -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 diff --git a/docs/cps-path.rst b/docs/cps-path.rst index e8a75d9cf..fba21f38c 100644 --- a/docs/cps-path.rst +++ b/docs/cps-path.rst @@ -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. diff --git a/docs/deployment.rst b/docs/deployment.rst index 46160c4f7..06e1ddcc0 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -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 | ``: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 | |