diff options
31 files changed, 564 insertions, 62 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index a18de2acdb..6aefda9c39 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -109,6 +109,8 @@ app: dmi: cm-events: topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events} + device-heartbeat: + topic: ${DMI_DEVICE_HEARTBEAT_TOPIC:dmi-device-heartbeat} notification: diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 63c5f16d04..e60e17418b 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -34,7 +34,7 @@ <artifactId>cps-ncmp-rest</artifactId> <properties> - <minimum-coverage>0.99</minimum-coverage> + <minimum-coverage>1.00</minimum-coverage> </properties> <dependencies> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java new file mode 100644 index 0000000000..816fc50675 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config.embeddedcache; + +import com.hazelcast.collection.ISet; +import com.hazelcast.config.SetConfig; +import org.onap.cps.cache.HazelcastCacheConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TrustLevelCacheConfig extends HazelcastCacheConfig { + + private static final SetConfig untrustworthyCmHandlesSetConfig = createSetConfig("untrustworthyCmHandlesSetConfig"); + + /** + * Untrustworthy cmhandle set instance. + * + * @return instance of distributed set of untrustworthy cmhandles. + */ + @Bean + public ISet<String> untrustworthyCmHandlesSet() { + return createHazelcastInstance("untrustworthyCmHandlesSet", untrustworthyCmHandlesSetConfig).getSet( + "untrustworthyCmHandlesSet"); + } + + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index 02de9854ef..ba6f89192d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -261,22 +261,22 @@ public class DmiDataOperations extends DmiOperations { final String topicName = dataOperationResourceUrlParameters.get("topic").get(0); final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0); - final MultiValueMap<String, Map<NcmpEventResponseCode, List<String>>> - cmHandleIdsPerResponseCodesPerOperationId = new LinkedMultiValueMap<>(); + final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode, List<String>>> + cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> { final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream() .map(CmHandle::getId).collect(Collectors.toList()); if (throwable.getCause() instanceof HttpClientRequestException) { - cmHandleIdsPerResponseCodesPerOperationId.add(dmiDataOperationRequestBody.getOperationId(), + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody, Map.of(NcmpEventResponseCode.UNABLE_TO_READ_RESOURCE_DATA, cmHandleIds)); } else { - cmHandleIdsPerResponseCodesPerOperationId.add(dmiDataOperationRequestBody.getOperationId(), + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody, Map.of(NcmpEventResponseCode.DMI_SERVICE_NOT_RESPONDING, cmHandleIds)); } }); ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId, - cmHandleIdsPerResponseCodesPerOperationId); + cmHandleIdsPerResponseCodesPerOperation); } } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java new file mode 100644 index 0000000000..458c1b8518 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java @@ -0,0 +1,71 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.trustlevel; + +import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; + +import com.hazelcast.collection.ISet; +import io.cloudevents.CloudEvent; +import io.cloudevents.kafka.impl.KafkaHeaders; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DeviceHeartbeatConsumer { + + private final ISet<String> untrustworthyCmHandlesSet; + + /** + * Listening the device heartbeats. + * + * @param deviceHeartbeatConsumerRecord Device Heartbeat record. + */ + @KafkaListener(topics = "${app.dmi.device-heartbeat.topic}", + containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory") + public void heartbeatListener(final ConsumerRecord<String, CloudEvent> deviceHeartbeatConsumerRecord) { + + final String cmHandleId = KafkaHeaders.getParsedKafkaHeader(deviceHeartbeatConsumerRecord.headers(), "ce_id"); + + final DeviceTrustLevel deviceTrustLevel = + toTargetEvent(deviceHeartbeatConsumerRecord.value(), DeviceTrustLevel.class); + + if (deviceTrustLevel == null || deviceTrustLevel.getTrustLevel() == null) { + log.warn("No or Invalid trust level defined"); + return; + } + + if (deviceTrustLevel.getTrustLevel().equals(TrustLevel.NONE)) { + untrustworthyCmHandlesSet.add(cmHandleId); + log.debug("Added cmHandleId to untrustworthy set : {}", cmHandleId); + } else if (deviceTrustLevel.getTrustLevel().equals(TrustLevel.COMPLETE) && untrustworthyCmHandlesSet.contains( + cmHandleId)) { + untrustworthyCmHandlesSet.remove(cmHandleId); + log.debug("Removed cmHandleId from untrustworthy set : {}", cmHandleId); + } + } + +} + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java new file mode 100644 index 0000000000..2ed4e45220 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java @@ -0,0 +1,37 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.trustlevel; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Data +@NoArgsConstructor +class DeviceTrustLevel implements Serializable { + + private static final long serialVersionUID = -1705715024067165212L; + + private TrustLevel trustLevel; + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java new file mode 100644 index 0000000000..f4254bb473 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java @@ -0,0 +1,25 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.trustlevel; + +public enum TrustLevel { + NONE, COMPLETE; +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java index 2d9a51b844..65cda94787 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java @@ -30,6 +30,7 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NcmpEventResponseCode; import org.onap.cps.ncmp.api.impl.events.NcmpCloudEventBuilder; +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation; import org.onap.cps.ncmp.events.async1_0_0.Data; import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent; import org.onap.cps.ncmp.events.async1_0_0.Response; @@ -44,49 +45,48 @@ public class DataOperationEventCreator { * * @param clientTopic topic the client wants to use for responses * @param requestId unique identifier per request - * @param cmHandleIdsPerResponseCodesPerOperationId map of cm handles per operation response per response code + * @param cmHandleIdsPerResponseCodesPerOperation map of cm handles per operation response per response code * @return Cloud Event */ public static CloudEvent createDataOperationEvent(final String clientTopic, final String requestId, - final MultiValueMap<String, + final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode, List<String>>> - cmHandleIdsPerResponseCodesPerOperationId) { + cmHandleIdsPerResponseCodesPerOperation) { final DataOperationEvent dataOperationEvent = new DataOperationEvent(); - final Data data = createPayloadFromDataOperationResponses(cmHandleIdsPerResponseCodesPerOperationId); + final Data data = createPayloadFromDataOperationResponses(cmHandleIdsPerResponseCodesPerOperation); dataOperationEvent.setData(data); final Map<String, String> extensions = createDataOperationExtensions(requestId, clientTopic); return NcmpCloudEventBuilder.builder().type(DataOperationEvent.class.getName()) .event(dataOperationEvent).extensions(extensions).setCloudEvent().build(); } - private static Data createPayloadFromDataOperationResponses(final MultiValueMap<String, Map<NcmpEventResponseCode, - List<String>>> cmHandleIdsPerOperationIdPerResponseCode) { + private static Data createPayloadFromDataOperationResponses(final MultiValueMap<DmiDataOperation, + Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperation) { final Data data = new Data(); final List<org.onap.cps.ncmp.events.async1_0_0.Response> responses = new ArrayList<>(); - cmHandleIdsPerOperationIdPerResponseCode.entrySet().forEach(cmHandleIdsPerOperationIdPerResponseCodeEntries -> - cmHandleIdsPerOperationIdPerResponseCodeEntries.getValue().forEach(cmHandleIdsPerResponseCodeEntries -> + cmHandleIdsPerResponseCodesPerOperation.forEach((dmiDataOperation, cmHandleIdsPerResponseCodes) -> + cmHandleIdsPerResponseCodes.forEach(cmHandleIdsPerResponseCodeEntries -> responses.addAll(createResponseFromDataOperationResponses( - cmHandleIdsPerOperationIdPerResponseCodeEntries.getKey(), - cmHandleIdsPerResponseCodeEntries) - ))); + dmiDataOperation, cmHandleIdsPerResponseCodeEntries)))); data.setResponses(responses); return data; } private static List<Response> createResponseFromDataOperationResponses( - final String operationId, + final DmiDataOperation dmiDataOperation, final Map<NcmpEventResponseCode, List<String>> cmHandleIdsPerResponseCodeEntries) { final List<org.onap.cps.ncmp.events.async1_0_0.Response> responses = new ArrayList<>(); - cmHandleIdsPerResponseCodeEntries.entrySet() - .forEach(cmHandleIdsPerResponseCodeEntry -> { - final Response response = new Response(); - response.setOperationId(operationId); - response.setStatusCode(cmHandleIdsPerResponseCodeEntry.getKey().getStatusCode()); - response.setStatusMessage(cmHandleIdsPerResponseCodeEntry.getKey().getStatusMessage()); - response.setIds(cmHandleIdsPerResponseCodeEntry.getValue()); - responses.add(response); - }); + cmHandleIdsPerResponseCodeEntries.forEach((ncmpEventResponseCode, cmHandleIds) -> { + final Response response = new Response(); + response.setOperationId(dmiDataOperation.getOperationId()); + response.setStatusCode(ncmpEventResponseCode.getStatusCode()); + response.setStatusMessage(ncmpEventResponseCode.getStatusMessage()); + response.setIds(cmHandleIds); + response.setResourceIdentifier(dmiDataOperation.getResourceIdentifier()); + response.setOptions(dmiDataOperation.getOptions()); + responses.add(response); + }); return responses; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java index d8fb904f21..c455337ac3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java @@ -68,8 +68,8 @@ public class ResourceDataOperationRequestUtils { final Collection<YangModelCmHandle> yangModelCmHandles) { final Map<String, List<DmiDataOperation>> dmiDataOperationsOutPerDmiServiceName = new HashMap<>(); - final MultiValueMap<String, Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperationId - = new LinkedMultiValueMap<>(); + final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode, + List<String>>> cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); final Set<String> nonReadyCmHandleIdsLookup = filterAndGetNonReadyCmHandleIds(yangModelCmHandles); final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName = @@ -100,15 +100,15 @@ public class ResourceDataOperationRequestUtils { } } } - populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperationId, - dataOperationDefinitionIn.getOperationId(), NcmpEventResponseCode.CM_HANDLES_NOT_FOUND, - nonExistingCmHandleIds); - populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperationId, - dataOperationDefinitionIn.getOperationId(), NcmpEventResponseCode.CM_HANDLES_NOT_READY, - nonReadyCmHandleIds); + populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation, + DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn), + NcmpEventResponseCode.CM_HANDLES_NOT_FOUND, nonExistingCmHandleIds); + populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation, + DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn), + NcmpEventResponseCode.CM_HANDLES_NOT_READY, nonReadyCmHandleIds); } - if (!cmHandleIdsPerResponseCodesPerOperationId.isEmpty()) { - publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperationId); + if (!cmHandleIdsPerResponseCodesPerOperation.isEmpty()) { + publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation); } return dmiDataOperationsOutPerDmiServiceName; } @@ -118,16 +118,16 @@ public class ResourceDataOperationRequestUtils { * * @param clientTopic client given topic * @param requestId unique identifier per request - * @param cmHandleIdsPerResponseCodesPerOperationId list of cm handle ids per operation id with response code + * @param cmHandleIdsPerResponseCodesPerOperation list of cm handle ids per operation with response code */ @Async public static void publishErrorMessageToClientTopic(final String clientTopic, final String requestId, - final MultiValueMap<String, + final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode, List<String>>> - cmHandleIdsPerResponseCodesPerOperationId) { + cmHandleIdsPerResponseCodesPerOperation) { final CloudEvent dataOperationCloudEvent = DataOperationEventCreator.createDataOperationEvent(clientTopic, - requestId, cmHandleIdsPerResponseCodesPerOperationId); + requestId, cmHandleIdsPerResponseCodesPerOperation); final EventsPublisher<CloudEvent> eventsPublisher = CpsApplicationContext.getCpsBean(EventsPublisher.class); eventsPublisher.publishCloudEvent(clientTopic, requestId, dataOperationCloudEvent); } @@ -174,14 +174,14 @@ public class ResourceDataOperationRequestUtils { != CmHandleState.READY).map(YangModelCmHandle::getId).collect(Collectors.toSet()); } - private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<String, - Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperationId, - final String operationId, + private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<DmiDataOperation, + Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperation, + final DmiDataOperation dmiDataOperation, final NcmpEventResponseCode ncmpEventResponseCode, final List<String> cmHandleIds) { if (!cmHandleIds.isEmpty()) { - cmHandleIdsPerResponseCodesPerOperationId.add(operationId, Map.of(ncmpEventResponseCode, cmHandleIds)); + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(ncmpEventResponseCode, cmHandleIds)); } } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy new file mode 100644 index 0000000000..48de23dca2 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy @@ -0,0 +1,102 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.trustlevel + +import com.fasterxml.jackson.databind.ObjectMapper +import com.hazelcast.collection.ISet +import io.cloudevents.CloudEvent +import io.cloudevents.core.builder.CloudEventBuilder +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.boot.test.context.SpringBootTest +import spock.lang.Specification + +@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) +class DeviceHeartbeatConsumerSpec extends Specification { + + def mockUntrustworthyCmHandlesSet = Mock(ISet<String>) + def objectMapper = new ObjectMapper() + + def objectUnderTest = new DeviceHeartbeatConsumer(mockUntrustworthyCmHandlesSet) + + def 'Operations to be done in an empty untrustworthy set for #scenario'() { + given: 'an event with trustlevel as #trustLevel' + def incomingEvent = testCloudEvent(trustLevel) + and: 'transformed as a kafka record' + def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', incomingEvent) + consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1')) + when: 'the event is consumed' + objectUnderTest.heartbeatListener(consumerRecord) + then: 'untrustworthy cmhandles are stored' + untrustworthyCmHandlesSetInvocationForAdd * mockUntrustworthyCmHandlesSet.add(_) + and: 'trustworthy cmHandles will be removed from untrustworthy set' + untrustworthyCmHandlesSetInvocationForContains * mockUntrustworthyCmHandlesSet.contains(_) + + where: 'below scenarios are applicable' + scenario | trustLevel || untrustworthyCmHandlesSetInvocationForAdd | untrustworthyCmHandlesSetInvocationForContains + 'None trust' | TrustLevel.NONE || 1 | 0 + 'Complete trust' | TrustLevel.COMPLETE || 0 | 1 + } + + def 'Invalid trust'() { + when: 'we provide an invalid trust in the event' + def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', testCloudEvent(null)) + consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1')) + objectUnderTest.heartbeatListener(consumerRecord) + then: 'no interaction with the untrustworthy cmhandles set' + 0 * mockUntrustworthyCmHandlesSet.add(_) + 0 * mockUntrustworthyCmHandlesSet.contains(_) + 0 * mockUntrustworthyCmHandlesSet.remove(_) + and: 'control flow returns without any exception' + noExceptionThrown() + + } + + def 'Remove trustworthy cmhandles from untrustworthy cmhandles set'() { + given: 'an event with COMPLETE trustlevel' + def incomingEvent = testCloudEvent(TrustLevel.COMPLETE) + and: 'transformed as a kafka record' + def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', incomingEvent) + consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1')) + and: 'untrustworthy cmhandles set contains cmhandle1' + 1 * mockUntrustworthyCmHandlesSet.contains(_) >> true + when: 'the event is consumed' + objectUnderTest.heartbeatListener(consumerRecord) + then: 'cmhandle removed from untrustworthy cmhandles set' + 1 * mockUntrustworthyCmHandlesSet.remove(_) >> { + args -> + { + args[0].equals('cmhandle1') + } + } + + } + + def testCloudEvent(trustLevel) { + return CloudEventBuilder.v1().withData(objectMapper.writeValueAsBytes(new DeviceTrustLevel(trustLevel))) + .withId("cmhandle1") + .withSource(URI.create('DMI')) + .withDataSchema(URI.create('test')) + .withType('org.onap.cm.events.trustlevel-notification') + .build() + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy index c866824062..38b2056d00 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy @@ -110,9 +110,9 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) and: 'data operation response event response size is 3' dataOperationResponseEvent.data.responses.size() == 3 - and: 'verify published response data as json string' - jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) - == '[{"operationId":"operational-14","ids":["unknown-cm-handle"],"statusCode":"100","statusMessage":"cm handle id(s) not found"},{"operationId":"operational-14","ids":["non-ready-cm handle"],"statusCode":"101","statusMessage":"cm handle(s) not ready"},{"operationId":"running-12","ids":["non-ready-cm handle"],"statusCode":"101","statusMessage":"cm handle(s) not ready"}]' + and: 'verify published data operation response as json string' + def dataOperationResponseEventJson = TestUtils.getResourceFileContent('dataOperationResponseEvent.json') + jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson } static def getYangModelCmHandles() { @@ -126,7 +126,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { 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) + new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) ] } } diff --git a/cps-ncmp-service/src/test/resources/dataOperationRequest.json b/cps-ncmp-service/src/test/resources/dataOperationRequest.json index d2e0d64892..f69b87631f 100644 --- a/cps-ncmp-service/src/test/resources/dataOperationRequest.json +++ b/cps-ncmp-service/src/test/resources/dataOperationRequest.json @@ -10,18 +10,20 @@ "ch3-dmi2", "unknown-cm-handle", "ch6-dmi1", - "non-ready-cm handle" + "non-ready-cm-handle" ] }, { "operation": "read", "operationId": "running-12", "datastore": "ncmp-datastore:passthrough-running", + "options": "some option", + "resourceIdentifier": "some resource identifier", "targetIds": [ "ch1-dmi1", "ch7-dmi2", "ch2-dmi1", - "non-ready-cm handle" + "non-ready-cm-handle" ] }, { @@ -29,6 +31,7 @@ "operationId": "operational-15", "datastore": "ncmp-datastore:passthrough-operational", "options": "some option", + "resourceIdentifier": "some resource identifier", "targetIds": [ "ch4-dmi2", "ch6-dmi1" diff --git a/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json new file mode 100644 index 0000000000..611d47d1a3 --- /dev/null +++ b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json @@ -0,0 +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 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 fd669b75c3..c30a63fd46 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 @@ -181,12 +181,46 @@ class QueryRestControllerSpec extends Specification { .andReturn().response then: 'the response contains the the datanode in json format' assert response.status == HttpStatus.OK.value() - assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedPageSize + assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedTotalPageSize assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}') where: 'the following options for include descendants are provided in the request' - scenario | pageIndex | pageSize | totalAnchors || expectedPageSize + scenario | pageIndex | pageSize | totalAnchors || expectedTotalPageSize '1st page with all anchors' | 1 | 3 | 3 || 1 '1st page with less anchors' | 1 | 2 | 3 || 2 } + + def 'Query data node across all anchors with pagination option with #scenario.'() { + given: 'service method returns a list containing a data node from different anchors' + def dataNode1 = new DataNodeBuilder().withXpath('/xpath') + .withAnchor('my_anchor') + .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() + def dataNode2 = new DataNodeBuilder().withXpath('/xpath') + .withAnchor('my_anchor_2') + .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build() + and: 'the query endpoint' + def dataspaceName = 'my_dataspace' + def cpsPath = 'some/cps/path' + def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query" + mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, + INCLUDE_ALL_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2] + mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2 + when: 'query data nodes API is invoked' + def response = + mvc.perform( + get(dataNodeEndpoint) + .param('cps-path', cpsPath) + .param('descendants', "all") + .param(parameterName, "1")) + .andReturn().response + then: 'the response contains the the datanode in json format' + assert response.status == HttpStatus.OK.value() + assert Integer.valueOf(response.getHeaderValue("total-pages")) == 1 + assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') + assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}') + where: + scenario | parameterName + 'only page size' | 'pageSize' + 'only page index' | 'pageIndex' + } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java index 6f9f5a482c..847a4a32fe 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java @@ -171,6 +171,18 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic anchorRepository.deleteAllByDataspaceAndNameIn(dataspaceEntity, anchorNames); } + @Transactional + @Override + public void updateAnchorSchemaSet(final String dataspaceName, + final String anchorName, + final String schemaSetName) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final SchemaSetEntity schemaSetEntity = schemaSetRepository + .getByDataspaceAndName(dataspaceEntity, schemaSetName); + anchorRepository.updateAnchorSchemaSetId(schemaSetEntity.getId(), anchorEntity.getId()); + } + private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java index 5bb5857810..b8503a7fea 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java @@ -99,4 +99,8 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Long> { deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0])); } + @Modifying + @Query(value = "UPDATE anchor SET schema_set_id =:schemaSetId WHERE id = :anchorId ", nativeQuery = true) + void updateAnchorSchemaSetId(@Param("schemaSetId") int schemaSetId, @Param("anchorId") long anchorId); + } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java index 4f056c8f6e..c187f20ea9 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java @@ -94,7 +94,7 @@ public class FragmentPrefetchRepositoryImpl implements FragmentPrefetchRepositor final FragmentEntity fragmentEntity = new FragmentEntity(); fragmentEntity.setId(resultSet.getLong("id")); fragmentEntity.setXpath(resultSet.getString("xpath")); - fragmentEntity.setParentId(resultSet.getLong("parentId")); + fragmentEntity.setParentId(resultSet.getObject("parentId", Long.class)); fragmentEntity.setAttributes(resultSet.getString("attributes")); fragmentEntity.setAnchor(anchorEntityPerId.get(resultSet.getLong("anchorId"))); fragmentEntity.setChildFragments(new HashSet<>()); diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java index fcf3f54cce..edd052a51c 100755 --- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java @@ -135,4 +135,13 @@ public interface CpsAdminService { * given module names */ Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames); + + /** + * Update schema set of an anchor. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param schemaSetName schema set name + */ + void updateAnchorSchemaSet(String dataspaceName, String anchorName, String schemaSetName); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java index e286eea173..d83ee434de 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java @@ -120,4 +120,11 @@ public class CpsAdminServiceImpl implements CpsAdminService { final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames); return anchors.stream().map(Anchor::getName).collect(Collectors.toList()); } + + @Override + public void updateAnchorSchemaSet(final String dataspaceName, + final String anchorName, + final String schemaSetName) { + cpsAdminPersistenceService.updateAnchorSchemaSet(dataspaceName, anchorName, schemaSetName); + } } diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java index 405e6e2a88..067191b5a5 100644 --- a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java +++ b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java @@ -24,6 +24,7 @@ import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; import com.hazelcast.config.NamedConfig; import com.hazelcast.config.QueueConfig; +import com.hazelcast.config.SetConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import lombok.extern.slf4j.Slf4j; @@ -57,6 +58,10 @@ public class HazelcastCacheConfig { if (namedConfig instanceof QueueConfig) { config.addQueueConfig((QueueConfig) namedConfig); } + if (namedConfig instanceof SetConfig) { + config.addSetConfig((SetConfig) namedConfig); + } + config.setClusterName(clusterName); updateDiscoveryMode(config); return config; @@ -76,6 +81,13 @@ public class HazelcastCacheConfig { return commonQueueConfig; } + protected static SetConfig createSetConfig(final String configName) { + final SetConfig commonSetConfig = new SetConfig(configName); + commonSetConfig.setBackupCount(1); + commonSetConfig.setAsyncBackupCount(1); + return commonSetConfig; + } + protected void updateDiscoveryMode(final Config config) { if (cacheKubernetesEnabled) { log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName); diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java index 1c1e80a20f..5a1810f473 100755 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java @@ -133,4 +133,13 @@ public interface CpsAdminPersistenceService { * @param anchorNames anchor names */ void deleteAnchors(String dataspaceName, Collection<String> anchorNames); + + /** + * Delete anchors by name in given dataspace. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param schemaSetName schema set name + */ + void updateAnchorSchemaSet(String dataspaceName, String anchorName, String schemaSetName); } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy index eb41e2085f..12564fb6d4 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy @@ -178,4 +178,11 @@ class CpsAdminServiceImplSpec extends Specification { and: 'the CpsValidator is called on the dataspaceName' 1 * mockCpsValidator.validateNameCharacters('someDataspace') } + + def 'Update anchor schema set.'() { + when: 'update anchor is invoked' + objectUnderTest.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName') + then: 'associated persistence service method is invoked with correct parameter' + 1 * mockCpsAdminPersistenceService.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName') + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy index 8efd48547e..415e9fd496 100644 --- a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy @@ -45,10 +45,17 @@ class HazelcastCacheConfigSpec extends Specification { } 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 - 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false - 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true + scenario | config || expectMapConfig | expectQueueConfig | expectSetConfig + 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false | false + 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true | false + 'Set Config' | HazelcastCacheConfig.createSetConfig('my set config') || false | false | true } } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index cd70cf8be7..3f672ad626 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -39,6 +39,7 @@ 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 Features -------- 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 4780e36428..03ef9c2fdc 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 @@ -108,7 +108,7 @@ class CpsIntegrationSpecBase extends Specification { def dataspaceExists(dataspaceName) { try { cpsAdminService.getDataspace(dataspaceName) - } catch (DataspaceNotFoundException e) { + } catch (DataspaceNotFoundException dataspaceNotFoundException) { return false } return true diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy index 92fbdaaa25..bdd894c31f 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy @@ -22,10 +22,12 @@ package org.onap.cps.integration.functional import org.onap.cps.api.CpsAdminService import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.AnchorNotFoundException import org.onap.cps.spi.exceptions.DataspaceInUseException import org.onap.cps.spi.exceptions.DataspaceNotFoundException +import java.time.OffsetDateTime class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { @@ -44,8 +46,8 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { def thrown = null try { objectUnderTest.getDataspace('newDataspace') - } catch(Exception e) { - thrown = e + } catch(Exception exception) { + thrown = exception } assert thrown instanceof DataspaceNotFoundException } @@ -100,8 +102,8 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { def thrown = null try { objectUnderTest.getAnchor(GENERAL_TEST_DATASPACE, 'newAnchor') - } catch(Exception e) { - thrown = e + } catch(Exception exception) { + thrown = exception } assert thrown instanceof AnchorNotFoundException } @@ -151,4 +153,28 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { 'just unknown module(s)' | GENERAL_TEST_DATASPACE } + def 'Update anchor schema set.'() { + when: 'a new schema set with tree yang model is created' + def newTreeYangModelAsString = readResourceDataFile('tree/new-test-tree.yang') + cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, 'newTreeSchemaSet', [tree: newTreeYangModelAsString]) + then: 'an anchor with new schema set is created' + objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, 'newTreeSchemaSet', 'anchor4') + and: 'the new tree datanode is saved' + def treeJsonData = readResourceDataFile('tree/new-test-tree.json') + cpsDataService.saveData(GENERAL_TEST_DATASPACE, 'anchor4', treeJsonData, OffsetDateTime.now()) + and: 'saved tree data node can be retrieved by its normalized xpath' + def branchName = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'anchor4', "/test-tree/branch", FetchDescendantsOption.DIRECT_CHILDREN_ONLY)[0].leaves['name'] + assert branchName == 'left' + and: 'a another schema set with updated tree yang model is created' + def updatedTreeYangModelAsString = readResourceDataFile('tree/updated-test-tree.yang') + cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, 'anotherTreeSchemaSet', [tree: updatedTreeYangModelAsString]) + and: 'anchor4 schema set is updated with another schema set successfully' + objectUnderTest.updateAnchorSchemaSet(GENERAL_TEST_DATASPACE, 'anchor4', 'anotherTreeSchemaSet') + when: 'updated tree data node with new leaves' + def updatedTreeJsonData = readResourceDataFile('tree/updated-test-tree.json') + cpsDataService.updateNodeLeaves(GENERAL_TEST_DATASPACE, "anchor4", "/test-tree/branch[@name='left']", updatedTreeJsonData, OffsetDateTime.now()) + then: 'updated tree data node can be retrieved by its normalized xpath' + def birdsName = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'anchor4',"/test-tree/branch[@name='left']/nest", FetchDescendantsOption.DIRECT_CHILDREN_ONLY)[0].leaves['birds'] + assert birdsName as String == '[Raven, Night Owl, Crow]' + } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy index 9716cb56eb..82a415ecef 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy @@ -394,6 +394,17 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { restoreBookstoreDataAnchor(1) } + def 'Update bookstore top-level container data node.'() { + when: 'the bookstore top-level container is updated' + def json = '{ "bookstore": { "bookstore-name": "new bookstore" }}' + objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now) + then: 'bookstore name has been updated' + def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', DIRECT_CHILDREN_ONLY) + result.leaves.'bookstore-name'[0] == 'new bookstore' + cleanup: + restoreBookstoreDataAnchor(1) + } + def 'Update multiple data node leaves.'() { given: 'Updated json for bookstore data' def jsonData = "{'book-store:books':{'lang':'English/French','price':100,'title':'Matilda'}}" diff --git a/integration-test/src/test/resources/data/tree/new-test-tree.json b/integration-test/src/test/resources/data/tree/new-test-tree.json new file mode 100644 index 0000000000..f7aefc472d --- /dev/null +++ b/integration-test/src/test/resources/data/tree/new-test-tree.json @@ -0,0 +1,12 @@ +{ + "test-tree": { + "branch": [ + { + "name": "left", + "nest": { + "name": "small" + } + } + ] + } +}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/tree/new-test-tree.yang b/integration-test/src/test/resources/data/tree/new-test-tree.yang new file mode 100644 index 0000000000..1a08b92f14 --- /dev/null +++ b/integration-test/src/test/resources/data/tree/new-test-tree.yang @@ -0,0 +1,21 @@ +module test-tree { + yang-version 1.1; + + namespace "org:onap:cps:test:test-tree"; + prefix tree; + revision "2020-02-02"; + + container test-tree { + list branch { + key "name"; + leaf name { + type string; + } + container nest { + leaf name { + type string; + } + } + } + } +}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/tree/updated-test-tree.json b/integration-test/src/test/resources/data/tree/updated-test-tree.json new file mode 100644 index 0000000000..2c2eea4f0d --- /dev/null +++ b/integration-test/src/test/resources/data/tree/updated-test-tree.json @@ -0,0 +1,10 @@ +{ + "nest": { + "name": "small", + "birds": [ + "Night Owl", + "Raven", + "Crow" + ] + } +}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/tree/updated-test-tree.yang b/integration-test/src/test/resources/data/tree/updated-test-tree.yang new file mode 100644 index 0000000000..bd883e8b6d --- /dev/null +++ b/integration-test/src/test/resources/data/tree/updated-test-tree.yang @@ -0,0 +1,33 @@ +module test-tree { + yang-version 1.1; + + namespace "org:onap:cps:test:test-tree"; + prefix tree; + + revision "2023-08-17" { + description + "added list of birds to nest"; + } + + revision "2020-09-15" { + description + "Sample Model"; + } + + container test-tree { + list branch { + key "name"; + leaf name { + type string; + } + container nest { + leaf name { + type string; + } + leaf-list birds { + type string; + } + } + } + } +}
\ No newline at end of file |