aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java87
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java41
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java25
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java25
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilter.java58
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDog.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDog.java)26
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy65
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy59
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelTest.groovy (renamed from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilterSpec.groovy)30
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy (renamed from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDogSpec.groovy)38
-rw-r--r--cps-rest/docs/openapi/components.yml26
-rw-r--r--cps-rest/docs/openapi/cpsDataV2.yml33
-rw-r--r--cps-rest/docs/openapi/openapi.yml3
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java18
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy21
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java17
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDeltaService.java42
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java20
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java108
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginStatus.java)27
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java79
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy33
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy66
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy6
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy52
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy83
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy17
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy97
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy4
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy4
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy24
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy16
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy30
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy8
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy31
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy8
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy4
-rw-r--r--integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java41
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstore.yang11
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json192
42 files changed, 1247 insertions, 334 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index 1f87a1ef9..db7b12cbc 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -105,7 +105,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
@Override
public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
- final DmiPluginRegistration dmiPluginRegistration) {
+ final DmiPluginRegistration dmiPluginRegistration) {
dmiPluginRegistration.validateDmiPluginRegistration();
final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
@@ -113,23 +113,23 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
if (!dmiPluginRegistration.getRemovedCmHandles().isEmpty()) {
dmiPluginRegistrationResponse.setRemovedCmHandles(
- parseAndProcessDeletedCmHandlesInRegistration(dmiPluginRegistration.getRemovedCmHandles()));
+ parseAndProcessDeletedCmHandlesInRegistration(dmiPluginRegistration.getRemovedCmHandles()));
}
if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
dmiPluginRegistrationResponse.setCreatedCmHandles(
- parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration));
+ parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration));
populateTrustLevelPerCmHandleCache(dmiPluginRegistration);
}
if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
dmiPluginRegistrationResponse.setUpdatedCmHandles(
- networkCmProxyDataServicePropertyHandler
- .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
+ networkCmProxyDataServicePropertyHandler
+ .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
}
if (dmiPluginRegistration.getUpgradedCmHandles() != null
- && !dmiPluginRegistration.getUpgradedCmHandles().getCmHandles().isEmpty()) {
+ && !dmiPluginRegistration.getUpgradedCmHandles().getCmHandles().isEmpty()) {
dmiPluginRegistrationResponse.setUpgradedCmHandles(
- parseAndProcessUpgradedCmHandlesInRegistration(dmiPluginRegistration));
+ parseAndProcessUpgradedCmHandlesInRegistration(dmiPluginRegistration));
}
return dmiPluginRegistrationResponse;
@@ -143,10 +143,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final String topicParamInQuery,
final String requestId) {
final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(datastoreName, cmHandleId,
- resourceIdentifier,
- optionsParamInQuery,
- topicParamInQuery,
- requestId);
+ resourceIdentifier,
+ optionsParamInQuery,
+ topicParamInQuery,
+ requestId);
return responseEntity.getBody();
}
@@ -156,13 +156,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final String resourceIdentifier,
final FetchDescendantsOption fetchDescendantsOption) {
return cpsDataService.getDataNodes(datastoreName, cmHandleId, resourceIdentifier,
- fetchDescendantsOption).iterator().next();
+ fetchDescendantsOption).iterator().next();
}
@Override
public void executeDataOperationForCmHandles(final String topicParamInQuery,
final DataOperationRequest
- dataOperationRequest,
+ dataOperationRequest,
final String requestId) {
dmiDataOperations.requestResourceDataFromDmi(topicParamInQuery, dataOperationRequest, requestId);
}
@@ -174,7 +174,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
final String requestData,
final String dataType) {
return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier,
- operationType, requestData, dataType);
+ operationType, requestData, dataType);
}
@Override
@@ -195,9 +195,9 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
*/
@Override
public Collection<NcmpServiceCmHandle> executeCmHandleSearch(
- final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+ final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
- cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
+ cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
return networkCmProxyCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters);
}
@@ -211,7 +211,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
@Override
public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
- cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
+ cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
return networkCmProxyCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters);
}
@@ -220,7 +220,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
* Set the data sync enabled flag, along with the data sync state
* based on the data sync enabled boolean for the cm handle id provided.
*
- * @param cmHandleId cm handle id
+ * @param cmHandleId cm handle id
* @param dataSyncEnabledTargetValue data sync enabled flag
*/
@Override
@@ -232,18 +232,18 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
if (CmHandleState.READY.equals(compositeState.getCmHandleState())) {
final DataStoreSyncState dataStoreSyncState = compositeState.getDataStores()
- .getOperationalDataStore().getDataStoreSyncState();
+ .getOperationalDataStore().getDataStoreSyncState();
if (Boolean.FALSE.equals(dataSyncEnabledTargetValue)
- && DataStoreSyncState.SYNCHRONIZED.equals(dataStoreSyncState)) {
+ && DataStoreSyncState.SYNCHRONIZED.equals(dataStoreSyncState)) {
// TODO : This is hard-coded for onap dmi that need to be addressed
cpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
- "/netconf-state", OffsetDateTime.now());
+ "/netconf-state", OffsetDateTime.now());
}
CompositeStateUtils.setDataSyncEnabledFlagWithDataSyncState(dataSyncEnabledTargetValue, compositeState);
inventoryPersistence.saveCmHandleState(cmHandleId, compositeState);
} else {
throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. Cm handle state is: "
- + compositeState.getCmHandleState());
+ + compositeState.getCmHandleState());
}
}
@@ -266,7 +266,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
*/
@Override
public Collection<String> executeCmHandleIdSearchForInventory(
- final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+ final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES);
return networkCmProxyCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters);
}
@@ -280,7 +280,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
@Override
public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) {
return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(
- inventoryPersistence.getYangModelCmHandle(cmHandleId));
+ inventoryPersistence.getYangModelCmHandle(cmHandleId));
}
/**
@@ -316,27 +316,26 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
* @return cm-handle registration response for create cm-handle requests.
*/
public List<CmHandleRegistrationResponse> parseAndProcessCreatedCmHandlesInRegistration(
- final DmiPluginRegistration dmiPluginRegistration) {
+ final DmiPluginRegistration dmiPluginRegistration) {
final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>();
- dmiPluginRegistration.getCreatedCmHandles()
- .forEach(cmHandle -> {
- final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(
- dmiPluginRegistration.getDmiPlugin(),
- dmiPluginRegistration.getDmiDataPlugin(),
- dmiPluginRegistration.getDmiModelPlugin(),
- cmHandle,
- cmHandle.getModuleSetTag());
- cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED);
- });
+ dmiPluginRegistration.getCreatedCmHandles().forEach(cmHandle -> {
+ final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(
+ dmiPluginRegistration.getDmiPlugin(),
+ dmiPluginRegistration.getDmiDataPlugin(),
+ dmiPluginRegistration.getDmiModelPlugin(),
+ cmHandle,
+ cmHandle.getModuleSetTag());
+ cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED);
+ });
return registerNewCmHandles(cmHandleStatePerCmHandle);
}
protected List<CmHandleRegistrationResponse> parseAndProcessDeletedCmHandlesInRegistration(
- final List<String> tobeRemovedCmHandles) {
+ final List<String> tobeRemovedCmHandles) {
final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
- new ArrayList<>(tobeRemovedCmHandles.size());
+ new ArrayList<>(tobeRemovedCmHandles.size());
final Collection<YangModelCmHandle> yangModelCmHandles =
- inventoryPersistence.getYangModelCmHandles(tobeRemovedCmHandles);
+ inventoryPersistence.getYangModelCmHandles(tobeRemovedCmHandles);
updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETING);
@@ -367,7 +366,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
protected List<CmHandleRegistrationResponse> parseAndProcessUpgradedCmHandlesInRegistration(
- final DmiPluginRegistration dmiPluginRegistration) {
+ final DmiPluginRegistration dmiPluginRegistration) {
final List<String> upgradedCmHandleIds = dmiPluginRegistration.getUpgradedCmHandles().getCmHandles();
@@ -446,7 +445,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
private void deleteCmHandleFromDbAndModuleSyncMap(final String cmHandleId) {
inventoryPersistence.deleteSchemaSetWithCascade(cmHandleId);
inventoryPersistence.deleteDataNode(NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId
- + "']");
+ + "']");
removeDeletedCmHandleFromModuleSyncMap(cmHandleId);
}
@@ -458,8 +457,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
private Collection<String> mapCmHandleIdsToXpaths(final Collection<String> cmHandles) {
return cmHandles.stream()
- .map(cmHandleId -> NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']")
- .collect(Collectors.toSet());
+ .map(cmHandleId -> NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']")
+ .collect(Collectors.toSet());
}
// CPS-1239 Robustness cleaning of in progress cache
@@ -470,7 +469,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
private List<CmHandleRegistrationResponse> registerNewCmHandles(final Map<YangModelCmHandle, CmHandleState>
- cmHandleStatePerCmHandle) {
+ cmHandleStatePerCmHandle) {
final List<String> cmHandleIds = getCmHandleIds(cmHandleStatePerCmHandle);
try {
lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
@@ -495,7 +494,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
private List<CmHandleRegistrationResponse> upgradeCmHandles(final Map<YangModelCmHandle, CmHandleState>
- cmHandleStatePerCmHandle) {
+ cmHandleStatePerCmHandle) {
final List<String> cmHandleIds = getCmHandleIds(cmHandleStatePerCmHandle);
log.info("Moving cm handles : {} into locked (for upgrade) state.", cmHandleIds);
try {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
index 25ded162b..be6a40198 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
@@ -74,8 +74,8 @@ public class NetworkCmProxyDataServicePropertyHandler {
cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
} catch (final DataNodeNotFoundException e) {
log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", cmHandleId, e.getMessage());
- cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
- .createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
+ cmHandleRegistrationResponses.add(
+ CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
} catch (final DataValidationException e) {
log.error("Unable to update cm handle : {}, caused by : {}", cmHandleId, e.getMessage());
cmHandleRegistrationResponses.add(
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
index 4ef4003cd..b6eb09218 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
@@ -22,12 +22,11 @@
package org.onap.cps.ncmp.api.impl.client;
import com.fasterxml.jackson.databind.JsonNode;
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability.DmiPluginStatus;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -37,19 +36,21 @@ import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
@Component
-@AllArgsConstructor
+@RequiredArgsConstructor
@Slf4j
public class DmiRestClient {
private static final String HEALTH_CHECK_URL_EXTENSION = "/actuator/health";
- private RestTemplate restTemplate;
- private DmiProperties dmiProperties;
+ private static final String EMPTY_STRING = "";
+ private final RestTemplate restTemplate;
+ private final DmiProperties dmiProperties;
/**
* Sends POST operation to DMI with json body containing module references.
- * @param dmiResourceUrl dmi resource url
+ *
+ * @param dmiResourceUrl dmi resource url
* @param requestBodyAsJsonString json data body
- * @param operationType the type of operation being executed (for error reporting only)
+ * @param operationType the type of operation being executed (for error reporting only)
* @return response entity of type String
*/
public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl,
@@ -61,28 +62,28 @@ public class DmiRestClient {
} catch (final HttpStatusCodeException httpStatusCodeException) {
final String exceptionMessage = "Unable to " + operationType.toString() + " resource data.";
throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
- httpStatusCodeException.getStatusCode().value());
+ httpStatusCodeException.getStatusCode().value());
}
}
/**
- * Sends GET operation to DMI plugin's health check URL.
+ * Get DMI plugin health status.
*
* @param dmiPluginBaseUrl the base URL of the dmi-plugin
- * @return DmiPluginStatus as UP or DOWN
+ * @return plugin health status ("UP" is all OK, EMPTY_STRING in case of any exception)
*/
- public DmiPluginStatus getDmiPluginStatus(final String dmiPluginBaseUrl) {
+ public String getDmiHealthStatus(final String dmiPluginBaseUrl) {
+ final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders()));
try {
- final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders()));
- final JsonNode dmiPluginHealthStatus = restTemplate
- .getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION, JsonNode.class, httpHeaders);
- if (dmiPluginHealthStatus != null && dmiPluginHealthStatus.get("status").asText().equals("UP")) {
- return DmiPluginStatus.UP;
- }
- } catch (final Exception exception) {
- log.warn("Could not send request for health check since {}", exception.getMessage());
+ final JsonNode responseHealthStatus =
+ restTemplate.getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION,
+ JsonNode.class, httpHeaders);
+ return responseHealthStatus == null ? EMPTY_STRING :
+ responseHealthStatus.get("status").asText();
+ } catch (final Exception e) {
+ log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiPluginBaseUrl, e.getMessage());
+ return EMPTY_STRING;
}
- return DmiPluginStatus.DOWN;
}
private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java
index 419d0a345..2d7ad698c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java
@@ -36,7 +36,6 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.onap.cps.ncmp.api.impl.inventory.enums.PropertyType;
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelFilter;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
@@ -50,6 +49,7 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
private static final String DESCENDANT_PATH = "//";
private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
private final CpsDataPersistenceService cpsDataPersistenceService;
+ private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
private final Map<String, TrustLevel> trustLevelPerCmHandle;
private final CpsValidator cpsValidator;
@@ -68,8 +68,7 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
final String trustLevelProperty = trustLevelPropertyQueryPairs.values().iterator().next();
final TrustLevel targetTrustLevel = TrustLevel.valueOf(trustLevelProperty);
- final TrustLevelFilter trustLevelFilter = new TrustLevelFilter(targetTrustLevel, trustLevelPerCmHandle);
- return trustLevelFilter.getAllCmHandleIdsByTargetTrustLevel();
+ return getCmHandleIdsByTrustLevel(targetTrustLevel);
}
@Override
@@ -117,6 +116,26 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
return cmHandleIds;
}
+ private Collection<String> getCmHandleIdsByTrustLevel(final TrustLevel targetTrustLevel) {
+ final Collection<String> selectedCmHandleIds = new HashSet<>();
+
+ for (final Map.Entry<String, TrustLevel> mapEntry : trustLevelPerDmiPlugin.entrySet()) {
+ final String dmiPluginIdentifier = mapEntry.getKey();
+ final TrustLevel dmiTrustLevel = mapEntry.getValue();
+ final Collection<String> candidateCmHandleIds = getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
+ for (final String candidateCmHandleId : candidateCmHandleIds) {
+ final TrustLevel candidateCmHandleTrustLevel = trustLevelPerCmHandle.get(candidateCmHandleId);
+ final TrustLevel effectiveTrustlevel =
+ candidateCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
+ if (targetTrustLevel.equals(effectiveTrustlevel)) {
+ selectedCmHandleIds.add(candidateCmHandleId);
+ }
+ }
+ }
+
+ return selectedCmHandleIds;
+ }
+
private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) {
return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet());
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java
index 8d1f8e90f..f130604a6 100644
--- 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
@@ -25,11 +25,28 @@ import lombok.Getter;
@Getter
public enum TrustLevel {
NONE(0), COMPLETE(99);
+ private final int level;
- private final int value;
+ /**
+ * Creates TrustLevel enum from a numeric value.
+ *
+ * @param level numeric value between 0-99
+ */
+ TrustLevel(final int level) {
+ this.level = level;
+ }
- TrustLevel(final int value) {
- this.value = value;
+ /**
+ * Gets the lower trust level (effective) among two.
+ *
+ * @param other the trust level compared with this
+ * @return the lower trust level
+ */
+ public final TrustLevel getEffectiveTrustLevel(final TrustLevel other) {
+ if (other.level < this.level) {
+ return other;
+ }
+ return this;
}
-} \ No newline at end of file
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilter.java
deleted file mode 100644
index 3b704ae4f..000000000
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * ============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.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import lombok.EqualsAndHashCode;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-@EqualsAndHashCode(onlyExplicitlyIncluded = true)
-public class TrustLevelFilter implements Comparable<TrustLevel> {
-
- @EqualsAndHashCode.Include
- private final TrustLevel targetTrustLevel;
- private final Map<String, TrustLevel> trustLevelPerCmHandle;
-
- @Override
- public int compareTo(@NonNull final TrustLevel other) {
- return Integer.compare(this.targetTrustLevel.getValue(), other.getValue());
- }
-
- /**
- * This method return cm handles that matches with given trust level.
- *
- * @return cm handle ids.
- */
- public Collection<String> getAllCmHandleIdsByTargetTrustLevel() {
- final Collection<String> resultCmHandleIds = new HashSet<>();
- trustLevelPerCmHandle.entrySet().forEach(cmHandleTrustLevelEntrySet -> {
- if (compareTo(cmHandleTrustLevelEntrySet.getValue()) == 0) {
- resultCmHandleIds.add(cmHandleTrustLevelEntrySet.getKey());
- }
- });
- return resultCmHandleIds;
- }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDog.java
index 39f880257..b073f1bc3 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDog.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDog.java
@@ -31,28 +31,24 @@ import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
-public class DMiPluginWatchDog {
-
- private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
+public class DmiPluginWatchDog {
private final DmiRestClient dmiRestClient;
+ private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
/**
- * Monitors the aliveness of DMI plugins by this watchdog.
- * This method periodically checks the health and status of each DMI plugin to ensure that
- * they are functioning properly. If a plugin is found to be unresponsive or in an
- * unhealthy state, the cache will be updated with the latest status.
- * The @fixedDelayString is the time interval, in milliseconds, between consecutive aliveness checks.
+ * This class monitors the trust level of all DMI plugin by checking the health status
+ * the resulting trustlevel wil be stored in the relevant cache.
+ * The @fixedDelayString is the time interval, in milliseconds, between consecutive checks.
*/
@Scheduled(fixedDelayString = "${ncmp.timers.trust-evel.dmi-availability-watchdog-ms:30000}")
- public void watchDmiPluginAliveness() {
- trustLevelPerDmiPlugin.keySet().forEach(dmiPluginName -> {
- final DmiPluginStatus dmiPluginStatus = dmiRestClient.getDmiPluginStatus(dmiPluginName);
- log.debug("Trust level for dmi-plugin: {} is {}", dmiPluginName, dmiPluginStatus.toString());
- if (DmiPluginStatus.UP.equals(dmiPluginStatus)) {
- trustLevelPerDmiPlugin.put(dmiPluginName, TrustLevel.COMPLETE);
+ public void watchDmiPluginTrustLevel() {
+ trustLevelPerDmiPlugin.keySet().forEach(dmiKey -> {
+ final String dmiHealthStatus = dmiRestClient.getDmiHealthStatus(dmiKey);
+ if ("UP".equals(dmiHealthStatus)) {
+ trustLevelPerDmiPlugin.put(dmiKey, TrustLevel.COMPLETE);
} else {
- trustLevelPerDmiPlugin.put(dmiPluginName, TrustLevel.NONE);
+ trustLevelPerDmiPlugin.put(dmiKey, TrustLevel.NONE);
}
});
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
index 80c0a27bf..c9ba5645f 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
@@ -25,8 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
-import org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability.DmiPluginStatus
import org.onap.cps.ncmp.utils.TestUtils
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
@@ -45,35 +45,30 @@ import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient, ObjectMapper])
+@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper])
class DmiRestClientSpec extends Specification {
@SpringBean
RestTemplate mockRestTemplate = Mock(RestTemplate)
@Autowired
+ NcmpConfiguration.DmiProperties dmiProperties
+
+ @Autowired
DmiRestClient objectUnderTest
@Autowired
ObjectMapper objectMapper
- def resourceUrl = 'some url'
- def mockResponseEntity = Mock(ResponseEntity)
- def dmiProperties = new NcmpConfiguration.DmiProperties()
-
- def setup() {
- dmiProperties.authUsername = 'test user'
- dmiProperties.authPassword = 'test pass'
- dmiProperties.dmiBasePath = 'dmi'
- }
+ def responseFromRestTemplate = Mock(ResponseEntity)
def 'DMI POST operation with JSON.'() {
- given: 'the rest template returns a valid response entity'
- mockRestTemplate.postForEntity(resourceUrl, _ as HttpEntity, Object.class) >> mockResponseEntity
+ given: 'the rest template returns a valid response entity for the expected parameters'
+ mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate
when: 'POST operation is invoked'
- def result = objectUnderTest.postOperationWithJsonData(resourceUrl, 'json-data', READ)
+ def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ)
then: 'the output of the method is equal to the output from the test template'
- result == mockResponseEntity
+ result == responseFromRestTemplate
}
def 'Failing DMI POST operation.'() {
@@ -93,40 +88,34 @@ class DmiRestClientSpec extends Specification {
operation << [CREATE, READ, PATCH]
}
- def 'Get dmi plugin health status #scenario'() {
- given: 'a health check response data as jsonNode'
+ def 'Dmi trust level is determined by spring boot health status'() {
+ given: 'a health check response'
def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
- ((ObjectNode) jsonNode).put('status', dmiAliveness);
- and: 'the rest template return a valid json node'
+ ((ObjectNode) jsonNode).put('status', 'my status')
mockRestTemplate.getForObject(*_) >> {jsonNode}
- when: 'get aliveness of the dmi plugin'
- def result = objectUnderTest.getDmiPluginStatus(resourceUrl)
- then: 'return value is equal to result of rest template call'
- result == expectedResult
- where: 'the following dmi aliveness are being used'
- scenario | dmiAliveness || expectedResult
- 'dmi plugin is UP' | 'UP' || DmiPluginStatus.UP
- 'dmi plugin is DOWN' | 'DOWN' || DmiPluginStatus.DOWN
+ when: 'get trust level of the dmi plugin'
+ def result = objectUnderTest.getDmiHealthStatus('some url')
+ then: 'the correct trust level is returned'
+ assert result == 'my status'
}
def 'Failing to get dmi plugin health status #scenario'() {
- given: 'the rest template return null'
- mockRestTemplate.getForObject(*_) >> {getResponse}
- when: 'get aliveness of the dmi plugin'
- def result = objectUnderTest.getDmiPluginStatus(resourceUrl)
- then: 'return value is equal to result of rest template call'
- result == expectedResult
- where: 'the following dmi responses are being used'
- scenario | getResponse || expectedResult
- 'get response is null' | null || DmiPluginStatus.DOWN
- 'get response throws exception' | {throw new Exception()} || DmiPluginStatus.DOWN
+ given: 'rest template with #scenario'
+ mockRestTemplate.getForObject(*_) >> healthStatusResponse
+ when: 'attempt to get health status of the dmi plugin'
+ def result = objectUnderTest.getDmiHealthStatus('some url')
+ then: 'result will be EMPTY_STRING "" '
+ assert result == ''
+ where: 'the following values are used'
+ scenario | healthStatusResponse
+ 'null' | null
+ 'exception' | {throw new Exception()}
}
def 'Basic auth header #scenario'() {
when: 'Specific dmi properties are provided'
dmiProperties.dmiBasicAuthEnabled = authEnabled
- objectUnderTest.dmiProperties = dmiProperties
then: 'http headers to conditionally have Authorization header'
assert (objectUnderTest.configureHttpHeaders(new HttpHeaders()).get('Authorization') != null) == isPresentInHttpHeader
where: 'the following configurations are used'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy
index 1da3a55a5..2f9d26494 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy
@@ -23,28 +23,27 @@ package org.onap.cps.ncmp.api.impl.inventory
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
import org.onap.cps.spi.utils.CpsValidator
-
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-
-import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueriesImpl
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.model.DataNode
import spock.lang.Shared
import spock.lang.Specification
class CmHandleQueriesImplSpec extends Specification {
- def cpsDataPersistenceService = Mock(CpsDataPersistenceService)
+
+ def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
+
+ def trustLevelPerDmiPlugin = [:]
+
+ def trustLevelPerCmHandle = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ]
+
def mockCpsValidator = Mock(CpsValidator)
- def trustLevelPerCmHandle = [ 'my completed cm handle': TrustLevel.COMPLETE, 'my untrusted cm handle': TrustLevel.NONE ]
- def objectUnderTest = new CmHandleQueriesImpl(cpsDataPersistenceService, trustLevelPerCmHandle, mockCpsValidator)
+ def objectUnderTest = new CmHandleQueriesImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator)
@Shared
def static sampleDataNodes = [new DataNode()]
@@ -74,13 +73,17 @@ class CmHandleQueriesImplSpec extends Specification {
}
def 'Query cm handles on trust level'() {
- given: 'query properties for trustlevel COMPLETE'
+ given: 'query properties for trust level COMPLETE'
def trustLevelPropertyQueryPairs = ['trustLevel' : TrustLevel.COMPLETE.toString()]
- when: 'the query is executed'
+ and: 'the dmi cache has been initialised and "knows" about my-dmi-plugin-identifier'
+ trustLevelPerDmiPlugin.put('my-dmi-plugin-identifier', TrustLevel.COMPLETE)
+ and: 'the DataNodes queried for a given cpsPath are returned from the persistence service'
+ mockResponses()
+ when: 'the query is run'
def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs)
- then: 'the result only contains the completed cm handle'
+ then: 'the result contain trusted PNFDemo'
assert result.size() == 1
- assert result[0] == 'my completed cm handle'
+ assert result[0] == 'PNFDemo'
}
def 'Query CmHandles using empty public properties query pair.'() {
@@ -99,7 +102,7 @@ class CmHandleQueriesImplSpec extends Specification {
def 'Query CmHandles by a private field\'s value.'() {
given: 'a data node exists with a certain additional-property'
- cpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5]
+ mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5]
when: 'a query on CmHandle private properties is executed using a map'
def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'])
then: 'one cm handle is returned'
@@ -110,7 +113,7 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to query'
def cmHandleState = CmHandleState.ADVISED
and: 'the persistence service returns a list of data nodes'
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
'//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes
when: 'cm handles are fetched by state'
def result = objectUnderTest.queryCmHandlesByState(cmHandleState)
@@ -122,7 +125,7 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to compare'
def cmHandleState = state
and: 'the persistence service returns a list of data nodes'
- cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state',
OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
when: 'cm handles are compared by state'
@@ -139,7 +142,7 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to query'
def cmHandleState = CmHandleState.READY
and: 'cps data service returns a list of data nodes'
- cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state',
OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
when: 'cm handles are fetched by state and id'
@@ -152,7 +155,7 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to query'
def cmHandleState = CmHandleState.READY
and: 'cps data service returns a list of data nodes'
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
'//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state'
def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED)
@@ -165,7 +168,7 @@ class CmHandleQueriesImplSpec extends Specification {
def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED'])
def cpsPath = '//cps-path'
and: 'cps data service returns a valid data node'
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS)
>> Arrays.asList(cmHandleDataNode)
when: 'get cm handles by cps path is invoked'
@@ -186,15 +189,15 @@ class CmHandleQueriesImplSpec extends Specification {
}
void mockResponses() {
- cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4]
- cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> []
- cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4]
- cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> []
- cpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3]
- cpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4]
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2]
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4]
- cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4]
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4]
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> []
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4]
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> []
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3]
+ mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4]
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2]
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4]
+ mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4]
}
def static createDataNode(dataNodeId) {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelTest.groovy
index 8f6621d24..9971f6307 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelFilterSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelTest.groovy
@@ -1,6 +1,6 @@
/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
+ * ============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.
@@ -9,7 +9,7 @@
* 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,
+ * 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.
@@ -20,22 +20,18 @@
package org.onap.cps.ncmp.api.impl.trustlevel
-
import spock.lang.Specification
-class TrustLevelFilterSpec extends Specification {
-
- def targetTrustLevel = TrustLevel.COMPLETE
-
- def trustLevelPerCmHandle = [ 'my completed cm handle': TrustLevel.COMPLETE, 'my untrusted cm handle': TrustLevel.NONE ]
+class TrustLevelTest extends Specification {
- def objectUnderTest = new TrustLevelFilter(targetTrustLevel, trustLevelPerCmHandle)
-
- def 'Obtain cm handle ids by a given trust level value'() {
- when: 'cm handles are retrieved'
- def result = objectUnderTest.getAllCmHandleIdsByTargetTrustLevel()
- then: 'the result only contains the completed cm handle'
- assert result.size() == 1
- assert result[0] == 'my completed cm handle'
+ def 'Get effective trust level between this and other.'() {
+ expect: 'the lower of two is returned'
+ assert effectiveLevel == current.getEffectiveTrustLevel(other)
+ where: 'the following trust level is used'
+ current | other || effectiveLevel
+ TrustLevel.COMPLETE | TrustLevel.NONE || TrustLevel.NONE
+ TrustLevel.NONE | TrustLevel.COMPLETE || TrustLevel.NONE
+ TrustLevel.COMPLETE | TrustLevel.COMPLETE || TrustLevel.COMPLETE
}
+
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy
index b6259bdf3..2771c4df1 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DMiPluginWatchDogSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy
@@ -24,27 +24,27 @@ import org.onap.cps.ncmp.api.impl.client.DmiRestClient
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
import spock.lang.Specification
-class DMiPluginWatchDogSpec extends Specification {
+class DmiPluginWatchDogSpec extends Specification {
-
- def mockTrustLevelPerDmiPlugin = Mock(Map<String, TrustLevel>)
def mockDmiRestClient = Mock(DmiRestClient)
- def objectUnderTest = new DMiPluginWatchDog(mockTrustLevelPerDmiPlugin, mockDmiRestClient)
-
-
- def 'watch dmi plugin aliveness'() {
- given: 'the dmi client returns aliveness for #dmi1Status'
- mockDmiRestClient.getDmiPluginStatus('dmi1') >> dmi1Status
- and: 'trust level cache returns dmi1'
- mockTrustLevelPerDmiPlugin.keySet() >> {['dmi1'] as Set}
- when: 'watch dog started'
- objectUnderTest.watchDmiPluginAliveness()
- then: 'trust level cache has been populated with #dmi1TrustLevel for dmi1'
- 1 * mockTrustLevelPerDmiPlugin.put('dmi1', dmi1TrustLevel)
- where: 'the following parameter are used'
- scenario | dmi1Status || dmi1TrustLevel
- 'dmi1 is UP' | DmiPluginStatus.UP || TrustLevel.COMPLETE
- 'dmi1 is DOWN' | DmiPluginStatus.DOWN || TrustLevel.NONE
+ def trustLevelPerDmiPlugin = [:]
+
+ def objectUnderTest = new DmiPluginWatchDog(mockDmiRestClient, trustLevelPerDmiPlugin)
+
+ def 'watch dmi plugin health status for #dmiHealhStatus'() {
+ given: 'the cache has been initialised and "knows" about dmi-1'
+ trustLevelPerDmiPlugin.put('dmi-1',null)
+ and: 'dmi client returns health status #dmiHealhStatus'
+ mockDmiRestClient.getDmiHealthStatus('dmi-1') >> dmiHealhStatus
+ when: 'dmi watch dog method runs'
+ objectUnderTest.watchDmiPluginTrustLevel()
+ then: 'the result is as expected'
+ assert trustLevelPerDmiPlugin.get('dmi-1') == expectedResult
+ where: 'the following health status is used'
+ dmiHealhStatus || expectedResult
+ 'UP' || TrustLevel.COMPLETE
+ 'Other' || TrustLevel.NONE
+ null || TrustLevel.NONE
}
}
diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml
index a3016ce76..c1b111bfa 100644
--- a/cps-rest/docs/openapi/components.yml
+++ b/cps-rest/docs/openapi/components.yml
@@ -137,6 +137,24 @@ components:
name: SciFi
- code: 02
name: kids
+ deltaReportSample:
+ value:
+ - action: "ADD"
+ xpath: "/bookstore/categories/[@code=3]"
+ target-data:
+ code: 3,
+ name: "kidz"
+ - action: "REMOVE"
+ xpath: "/bookstore/categories/[@code=1]"
+ source-data:
+ code: 1,
+ name: "Fiction"
+ - action: "UPDATE"
+ xpath: "/bookstore/categories/[@code=2]"
+ source-data:
+ name: "Funny"
+ target-data:
+ name: "Comic"
parameters:
dataspaceNameInQuery:
@@ -187,6 +205,14 @@ components:
schema:
type: string
example: my-anchor
+ targetAnchorNameInQuery:
+ name: target-anchor-name
+ in: query
+ description: target-anchor-name
+ required: true
+ schema:
+ type: string
+ example: my-anchor
xpathInQuery:
name: xpath
in: query
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml
index ad0c299d7..c7629b70e 100644
--- a/cps-rest/docs/openapi/cpsDataV2.yml
+++ b/cps-rest/docs/openapi/cpsDataV2.yml
@@ -46,4 +46,37 @@ nodeByDataspaceAndAnchor:
$ref: 'components.yml#/components/responses/Forbidden'
'500':
$ref: 'components.yml#/components/responses/InternalServerError'
+ x-codegen-request-body-name: xpath
+
+deltaByDataspaceAndAnchors:
+ get:
+ description: Get delta between two anchors within a given dataspace
+ tags:
+ - cps-data
+ summary: Get delta between anchors in the same dataspace
+ operationId: getDeltaByDataspaceAndAnchors
+ parameters:
+ - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+ - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+ - $ref: 'components.yml#/components/parameters/targetAnchorNameInQuery'
+ - $ref: 'components.yml#/components/parameters/xpathInQuery'
+ - $ref: 'components.yml#/components/parameters/descendantsInQuery'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ examples:
+ dataSample:
+ $ref: 'components.yml#/components/examples/deltaReportSample'
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
+ '401':
+ $ref: 'components.yml#/components/responses/Unauthorized'
+ '403':
+ $ref: 'components.yml#/components/responses/Forbidden'
+ '500':
+ $ref: 'components.yml#/components/responses/InternalServerError'
x-codegen-request-body-name: xpath \ No newline at end of file
diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml
index 4bbf9f0fb..f29335a0a 100644
--- a/cps-rest/docs/openapi/openapi.yml
+++ b/cps-rest/docs/openapi/openapi.yml
@@ -104,6 +104,9 @@ paths:
/{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
$ref: 'cpsData.yml#/listElementByDataspaceAndAnchor'
+ /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/delta:
+ $ref: 'cpsDataV2.yml#/deltaByDataspaceAndAnchors'
+
/v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
$ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath'
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index 60e7fb6d2..4f9328b6c 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -38,6 +38,7 @@ import org.onap.cps.api.CpsDataService;
import org.onap.cps.rest.api.CpsDataApi;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.DeltaReport;
import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
@@ -166,6 +167,23 @@ public class DataRestController implements CpsDataApi {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
+ @Override
+ @Timed(value = "cps.data.controller.get.delta",
+ description = "Time taken to get delta between anchors")
+ public ResponseEntity<Object> getDeltaByDataspaceAndAnchors(final String dataspaceName,
+ final String sourceAnchorName,
+ final String targetAnchorName,
+ final String xpath,
+ final String descendants) {
+ final FetchDescendantsOption fetchDescendantsOption =
+ FetchDescendantsOption.getFetchDescendantsOption(descendants);
+
+ final List<DeltaReport> deltaBetweenAnchors =
+ cpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchorName,
+ targetAnchorName, xpath, fetchDescendantsOption);
+ return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaBetweenAnchors), HttpStatus.OK);
+ }
+
private static boolean isRootXpath(final String xpath) {
return ROOT_XPATH.equals(xpath);
}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 81262c80c..12c9c4c60 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -30,6 +30,7 @@ import org.onap.cps.api.CpsDataService
import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.spi.model.DeltaReportBuilder
import org.onap.cps.utils.ContentType
import org.onap.cps.utils.DateTimeUtility
import org.onap.cps.utils.JsonObjectMapper
@@ -331,7 +332,25 @@ class DataRestControllerSpec extends Specification {
and: 'the response contains the root node identifier'
assert response.contentAsString.contains('parent')
and: 'the response contains child is true'
- assert response.contentAsString.contains('"child"') == true
+ assert response.contentAsString.contains('"child"')
+ }
+
+ def 'Get delta between two anchors'() {
+ given: 'the service returns a list containing delta reports'
+ def deltaReports = new DeltaReportBuilder().actionAdd().withXpath('/bookstore').withSourceData('bookstore-name': 'Easons').withTargetData('bookstore-name': 'Easons').build()
+ def xpath = 'some xpath'
+ def endpoint = "$dataNodeBaseEndpointV2/anchors/sourceAnchor/delta"
+ mockCpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, 'sourceAnchor', 'targetAnchor', xpath, OMIT_DESCENDANTS) >> [deltaReports]
+ when: 'get delta request is performed using REST API'
+ def response =
+ mvc.perform(get(endpoint)
+ .param('target-anchor-name', 'targetAnchor')
+ .param('xpath', xpath))
+ .andReturn().response
+ then: 'expected response code is returned'
+ assert response.status == HttpStatus.OK.value()
+ and: 'the response contains expected value'
+ assert response.contentAsString.contains("[{\"action\":\"add\",\"xpath\":\"/bookstore\",\"sourceData\":{\"bookstore-name\":\"Easons\"},\"targetData\":{\"bookstore-name\":\"Easons\"}}]")
}
def 'Update data node leaves: #scenario.'() {
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 6a2cac467..c9879595a 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
@@ -26,9 +26,11 @@ package org.onap.cps.api;
import java.time.OffsetDateTime;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.DeltaReport;
import org.onap.cps.utils.ContentType;
/*
@@ -298,4 +300,19 @@ public interface CpsDataService {
* @param timeoutInMilliseconds lock attempt timeout in milliseconds
*/
void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds);
+
+ /**
+ * Retrieves the delta between two anchors by xpath within a dataspace.
+ *
+ * @param dataspaceName dataspace name
+ * @param sourceAnchorName source anchor name
+ * @param targetAnchorName target anchor name
+ * @param xpath xpath
+ * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant
+ * nodes (recursively) as well
+ * @return list containing {@link DeltaReport} objects
+ */
+ List<DeltaReport> getDeltaByDataspaceAndAnchors(String dataspaceName, String sourceAnchorName,
+ String targetAnchorName, String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDeltaService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDeltaService.java
new file mode 100644
index 000000000..d806c208a
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDeltaService.java
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api;
+
+import java.util.Collection;
+import java.util.List;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.DeltaReport;
+
+public interface CpsDeltaService {
+
+ /**
+ * Retrieves delta between source data nodes and target data nodes. Source data nodes contain the data which acts as
+ * the point of reference for delta report, whereas target data nodes contain the data being compared against
+ * source data node. List of {@link DeltaReport}. Each Delta Report contains information such as action, xpath,
+ * source-payload and target-payload.
+ *
+ * @param sourceDataNodes collection of {@link DataNode} as source/reference for delta generation
+ * @param targetDataNodes collection of {@link DataNode} as target data for delta generation
+ * @return list of {@link DeltaReport} containing delta information
+ */
+ List<DeltaReport> getDeltaReports(Collection<DataNode> sourceDataNodes,
+ Collection<DataNode> targetDataNodes);
+}
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 1d68450f8..e74e0ad24 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
@@ -34,12 +34,14 @@ import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsAdminService;
import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsDeltaService;
import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.notification.NotificationService;
import org.onap.cps.notification.Operation;
@@ -49,6 +51,7 @@ import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.spi.model.Anchor;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.spi.model.DeltaReport;
import org.onap.cps.spi.utils.CpsValidator;
import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.TimedYangParser;
@@ -70,6 +73,7 @@ public class CpsDataServiceImpl implements CpsDataService {
private final NotificationService notificationService;
private final CpsValidator cpsValidator;
private final TimedYangParser timedYangParser;
+ private final CpsDeltaService cpsDeltaService;
@Override
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
@@ -215,6 +219,22 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
+ @Timed(value = "cps.data.service.get.delta",
+ description = "Time taken to get delta between anchors")
+ public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
+ final String sourceAnchorName,
+ final String targetAnchorName, final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+
+ final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
+ sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
+ final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
+ targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
+
+ return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
+ }
+
+ @Override
@Timed(value = "cps.data.service.datanode.descendants.update",
description = "Time taken to update a data node and descendants")
public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
new file mode 100644
index 000000000..683ddce3d
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
@@ -0,0 +1,108 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsDeltaService;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.DeltaReport;
+import org.onap.cps.spi.model.DeltaReportBuilder;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@NoArgsConstructor
+public class CpsDeltaServiceImpl implements CpsDeltaService {
+
+ @Override
+ public List<DeltaReport> getDeltaReports(final Collection<DataNode> sourceDataNodes,
+ final Collection<DataNode> targetDataNodes) {
+
+ final List<DeltaReport> deltaReport = new ArrayList<>();
+
+ final Map<String, DataNode> xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes);
+ final Map<String, DataNode> xpathToTargetDataNodes = convertToXPathToDataNodesMap(targetDataNodes);
+
+ deltaReport.addAll(getRemovedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes));
+
+ deltaReport.addAll(getAddedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes));
+
+ return Collections.unmodifiableList(deltaReport);
+ }
+
+ private static Map<String, DataNode> convertToXPathToDataNodesMap(
+ final Collection<DataNode> dataNodes) {
+ final Map<String, DataNode> xpathToDataNode = new LinkedHashMap<>();
+ for (final DataNode dataNode : dataNodes) {
+ xpathToDataNode.put(dataNode.getXpath(), dataNode);
+ final Collection<DataNode> childDataNodes = dataNode.getChildDataNodes();
+ if (!childDataNodes.isEmpty()) {
+ xpathToDataNode.putAll(convertToXPathToDataNodesMap(childDataNodes));
+ }
+ }
+ return xpathToDataNode;
+ }
+
+ private static List<DeltaReport> getRemovedDeltaReports(
+ final Map<String, DataNode> xpathToSourceDataNodes,
+ final Map<String, DataNode> xpathToTargetDataNodes) {
+
+ final List<DeltaReport> removedDeltaReportEntries = new ArrayList<>();
+ for (final Map.Entry<String, DataNode> entry: xpathToSourceDataNodes.entrySet()) {
+ final String xpath = entry.getKey();
+ final DataNode sourceDataNode = entry.getValue();
+ final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath);
+
+ if (targetDataNode == null) {
+ final Map<String, Serializable> sourceDataNodeLeaves = sourceDataNode.getLeaves();
+ final DeltaReport removedData = new DeltaReportBuilder().actionRemove().withXpath(xpath)
+ .withSourceData(sourceDataNodeLeaves).build();
+ removedDeltaReportEntries.add(removedData);
+ }
+ }
+ return removedDeltaReportEntries;
+ }
+
+ private static List<DeltaReport> getAddedDeltaReports(final Map<String, DataNode> xpathToSourceDataNodes,
+ final Map<String, DataNode> xpathToTargetDataNodes) {
+
+ final List<DeltaReport> addedDeltaReportEntries = new ArrayList<>();
+ final Map<String, DataNode> xpathToAddedNodes = new LinkedHashMap<>(xpathToTargetDataNodes);
+ xpathToAddedNodes.keySet().removeAll(xpathToSourceDataNodes.keySet());
+ for (final Map.Entry<String, DataNode> entry: xpathToAddedNodes.entrySet()) {
+ final String xpath = entry.getKey();
+ final DataNode dataNode = entry.getValue();
+ final DeltaReport addedDataForDeltaReport = new DeltaReportBuilder().actionAdd().withXpath(xpath)
+ .withTargetData(dataNode.getLeaves()).build();
+ addedDeltaReportEntries.add(addedDataForDeltaReport);
+ }
+ return addedDeltaReportEntries;
+ }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginStatus.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
index 352d36f94..b9c05dcf0 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginStatus.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
@@ -1,6 +1,6 @@
/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,25 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability;
+package org.onap.cps.spi.model;
-public enum DmiPluginStatus {
- UP, DOWN;
+import java.io.Serializable;
+import java.util.Map;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter(AccessLevel.PROTECTED)
+@Getter
+public class DeltaReport {
+
+ public static final String ADD_ACTION = "add";
+ public static final String REMOVE_ACTION = "remove";
+
+ DeltaReport() {}
+
+ private String action;
+ private String xpath;
+ private Map<String, Serializable> sourceData;
+ private Map<String, Serializable> targetData;
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
new file mode 100644
index 000000000..cef6ca3fa
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.model;
+
+import java.io.Serializable;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class DeltaReportBuilder {
+
+
+ private String action;
+ private String xpath;
+ private Map<String, Serializable> sourceData;
+ private Map<String, Serializable> targetData;
+
+ public DeltaReportBuilder withXpath(final String xpath) {
+ this.xpath = xpath;
+ return this;
+ }
+
+ public DeltaReportBuilder withSourceData(final Map<String, Serializable> sourceData) {
+ this.sourceData = sourceData;
+ return this;
+ }
+
+ public DeltaReportBuilder withTargetData(final Map<String, Serializable> targetData) {
+ this.targetData = targetData;
+ return this;
+ }
+
+ public DeltaReportBuilder actionAdd() {
+ this.action = DeltaReport.ADD_ACTION;
+ return this;
+ }
+
+ public DeltaReportBuilder actionRemove() {
+ this.action = DeltaReport.REMOVE_ACTION;
+ return this;
+ }
+
+ /**
+ * To create a single entry of {@link DeltaReport}.
+ *
+ * @return {@link DeltaReport}
+ */
+ public DeltaReport build() {
+ final DeltaReport deltaReport = new DeltaReport();
+ deltaReport.setAction(action);
+ deltaReport.setXpath(xpath);
+ if (sourceData != null && !sourceData.isEmpty()) {
+ deltaReport.setSourceData(sourceData);
+ }
+
+ if (targetData != null && !targetData.isEmpty()) {
+ deltaReport.setTargetData(targetData);
+ }
+ return deltaReport;
+ }
+}
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 e1d15d68a..a91459852 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
@@ -25,6 +25,7 @@ package org.onap.cps.api.impl
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDeltaService
import org.onap.cps.notification.NotificationService
import org.onap.cps.notification.Operation
import org.onap.cps.spi.CpsDataPersistenceService
@@ -37,12 +38,14 @@ import org.onap.cps.spi.exceptions.SessionTimeoutException
import org.onap.cps.spi.model.Anchor
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.spi.model.DeltaReportBuilder
+import org.onap.cps.spi.utils.CpsValidator
import org.onap.cps.utils.ContentType
import org.onap.cps.utils.TimedYangParser
import org.onap.cps.yang.YangTextSchemaSourceSet
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import spock.lang.Shared
import spock.lang.Specification
-import org.onap.cps.spi.utils.CpsValidator
import java.time.OffsetDateTime
import java.util.stream.Collectors
@@ -54,18 +57,28 @@ class CpsDataServiceImplSpec extends Specification {
def mockNotificationService = Mock(NotificationService)
def mockCpsValidator = Mock(CpsValidator)
def timedYangParser = new TimedYangParser()
+ def mockCpsDeltaService = Mock(CpsDeltaService);
def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
- mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser)
+ mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService)
def setup() {
+
mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
+ mockCpsAdminService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1
+ mockCpsAdminService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2
}
+ @Shared
+ static def ANCHOR_NAME_1 = 'some-anchor-1'
+ @Shared
+ static def ANCHOR_NAME_2 = 'some-anchor-2'
def dataspaceName = 'some-dataspace'
def anchorName = 'some-anchor'
def schemaSetName = 'some-schema-set'
def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
+ def anchor1 = Anchor.builder().name(ANCHOR_NAME_1).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
+ def anchor2 = Anchor.builder().name(ANCHOR_NAME_2).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
def observedTimestamp = OffsetDateTime.now()
def 'Saving #scenario data.'() {
@@ -228,6 +241,22 @@ class CpsDataServiceImplSpec extends Specification {
fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
}
+ def 'Get delta between 2 anchors'() {
+ given: 'some xpath, source and target data nodes'
+ def xpath = '/xpath'
+ def sourceDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
+ def targetDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
+ when: 'attempt to get delta between 2 anchors'
+ objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+ then: 'the dataspace and anchor names are validated'
+ 2 * mockCpsValidator.validateNameCharacters(_)
+ and: 'data nodes are fetched using appropriate persistence layer method'
+ mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
+ mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
+ and: 'appropriate delta service method is invoked once with correct source and target data nodes'
+ 1 * mockCpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes)
+ }
+
def 'Update data node leaves: #scenario.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
new file mode 100644
index 000000000..a4f433973
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl
+
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.spi.model.DataNodeBuilder
+import spock.lang.Shared
+import spock.lang.Specification
+
+class CpsDeltaServiceImplSpec extends Specification{
+
+ def objectUnderTest = new CpsDeltaServiceImpl()
+
+ @Shared
+ def dataNodeWithLeafAndChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload'])
+ .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").withLeaves('child-leaf': 'child-payload').build()]).build()]
+ @Shared
+ def dataNodeWithChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload'])
+ .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()]
+ @Shared
+ def emptyDataNode = [new DataNodeBuilder().withXpath('/parent').build()]
+
+ def 'Get delta between data nodes for removed data where source data node has #scenario'() {
+ when: 'attempt to get delta between 2 data nodes'
+ def result = objectUnderTest.getDeltaReports(sourceDataNode as Collection<DataNode>, emptyDataNode)
+ then: 'the delta report contains "remove" action with right data'
+ assert result.first().action.equals("remove")
+ assert result.first().xpath == "/parent/child"
+ assert result.first().sourceData == expectedSourceData
+ where: 'following data was used'
+ scenario | sourceDataNode || expectedSourceData
+ 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload']
+ 'no leaf data' | dataNodeWithChildDataNode || null
+ }
+
+ def 'Get delta between data nodes with new data where target data node has #scenario'() {
+ when: 'attempt to get delta between 2 data nodes'
+ def result = objectUnderTest.getDeltaReports(emptyDataNode, targetDataNode)
+ then: 'the delta report contains "add" action with right data'
+ assert result.first().action.equals("add")
+ assert result.first().xpath == "/parent/child"
+ assert result.first().targetData == expectedTargetData
+ where: 'following data was used'
+ scenario | targetDataNode || expectedTargetData
+ 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload']
+ 'no leaf data' | dataNodeWithChildDataNode || null
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
index 75f29746d..1b873ec12 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2023 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ package org.onap.cps.api.impl
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDeltaService
import org.onap.cps.notification.NotificationService
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
@@ -45,12 +46,13 @@ class E2ENetworkSliceSpec extends Specification {
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
def timedYangParser = new TimedYangParser()
+ def mockCpsDeltaService = Mock(CpsDeltaService)
def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAdminService,
- mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser)
+ mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy
new file mode 100644
index 000000000..e19d12042
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.model
+
+import spock.lang.Specification
+
+class DeltaReportBuilderSpec extends Specification{
+
+ def 'Generating delta report with for add action'() {
+ when: 'delta report is generated'
+ def result = new DeltaReportBuilder()
+ .actionAdd()
+ .withXpath('/xpath')
+ .withTargetData(['data':'leaf-data'])
+ .build()
+ then: 'the delta report contains the "add" action with expected target data'
+ assert result.action == 'add'
+ assert result.xpath == '/xpath'
+ assert result.targetData == ['data': 'leaf-data']
+ }
+
+ def 'Generating delta report with attributes for remove action'() {
+ when: 'delta report is generated'
+ def result = new DeltaReportBuilder()
+ .actionRemove()
+ .withXpath('/xpath')
+ .withSourceData(['data':'leaf-data'])
+ .build()
+ then: 'the delta report contains the "remove" action with expected source data'
+ assert result.action == 'remove'
+ assert result.xpath == '/xpath'
+ assert result.sourceData == ['data': 'leaf-data']
+ }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy
new file mode 100644
index 000000000..c42bfd7be
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * ============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.integration
+
+import java.util.concurrent.TimeUnit
+import spock.lang.Specification
+
+class ResourceMeterPerfTest extends Specification {
+
+ final int MEGABYTE = 1_000_000
+
+ def resourceMeter = new ResourceMeter()
+
+ def 'ResourceMeter accurately measures duration'() {
+ when: 'we measure how long a known operation takes'
+ resourceMeter.start()
+ TimeUnit.SECONDS.sleep(2)
+ resourceMeter.stop()
+ then: 'ResourceMeter reports a duration within 10ms of the expected duration'
+ assert resourceMeter.getTotalTimeInSeconds() >= 2
+ assert resourceMeter.getTotalTimeInSeconds() <= 2.01
+ }
+
+ def 'ResourceMeter reports memory usage when allocating a large byte array'() {
+ when: 'the resource meter is started'
+ resourceMeter.start()
+ and: 'some memory is allocated'
+ byte[] array = new byte[50 * MEGABYTE]
+ and: 'the resource meter is stopped'
+ resourceMeter.stop()
+ then: 'the reported memory usage is close to the amount of memory allocated'
+ assert resourceMeter.getTotalMemoryUsageInMB() >= 50
+ assert resourceMeter.getTotalMemoryUsageInMB() <= 55
+ }
+
+ def 'ResourceMeter measures PEAK memory usage when garbage collector runs'() {
+ when: 'the resource meter is started'
+ resourceMeter.start()
+ and: 'some memory is allocated'
+ byte[] array = new byte[50 * MEGABYTE]
+ and: 'the memory is garbage collected'
+ array = null
+ ResourceMeter.performGcAndWait()
+ and: 'the resource meter is stopped'
+ resourceMeter.stop()
+ then: 'the reported memory usage is close to the peak amount of memory allocated'
+ assert resourceMeter.getTotalMemoryUsageInMB() >= 50
+ assert resourceMeter.getTotalMemoryUsageInMB() <= 55
+ }
+
+ def 'ResourceMeter measures memory increase only during measurement'() {
+ given: '50 megabytes is allocated before measurement'
+ byte[] arrayBefore = new byte[50 * MEGABYTE]
+ when: 'memory is allocated during measurement'
+ resourceMeter.start()
+ byte[] arrayDuring = new byte[40 * MEGABYTE]
+ resourceMeter.stop()
+ and: '50 megabytes is allocated after measurement'
+ byte[] arrayAfter = new byte[50 * MEGABYTE]
+ then: 'the reported memory usage is close to the amount allocated DURING measurement'
+ assert resourceMeter.getTotalMemoryUsageInMB() >= 40
+ assert resourceMeter.getTotalMemoryUsageInMB() <= 45
+ }
+
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy
index 327a39ee4..14612d6c1 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2023 Nordix Foundation
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
@@ -26,17 +27,24 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase {
def static FUNCTIONAL_TEST_DATASPACE_1 = 'functionalTestDataspace1'
def static FUNCTIONAL_TEST_DATASPACE_2 = 'functionalTestDataspace2'
+ def static FUNCTIONAL_TEST_DATASPACE_3 = 'functionalTestDataspace3'
def static NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DATA = 2
+ def static NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DELTA_DATA = 1
def static BOOKSTORE_ANCHOR_1 = 'bookstoreAnchor1'
def static BOOKSTORE_ANCHOR_2 = 'bookstoreAnchor2'
+ def static BOOKSTORE_ANCHOR_3 = 'bookstoreSourceAnchor1'
+ def static BOOKSTORE_ANCHOR_4 = 'copyOfSourceAnchor1'
+ def static BOOKSTORE_ANCHOR_5 = 'bookstoreAnchorForDeltaReport1'
def static initialized = false
def static bookstoreJsonData = readResourceDataFile('bookstore/bookstoreData.json')
+ def static bookstoreJsonDataForDeltaReport = readResourceDataFile('bookstore/bookstoreDataForDeltaReport.json')
def setup() {
if (!initialized) {
setupBookstoreInfraStructure()
addBookstoreData()
+ addDeltaData()
initialized = true
}
}
@@ -44,9 +52,12 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase {
def setupBookstoreInfraStructure() {
cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_1)
cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_2)
+ cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_3)
def bookstoreYangModelAsString = readResourceDataFile('bookstore/bookstore.yang')
cpsModuleService.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, [bookstore: bookstoreYangModelAsString])
cpsModuleService.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_2, BOOKSTORE_SCHEMA_SET, [bookstore: bookstoreYangModelAsString])
+ cpsModuleService.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_SCHEMA_SET, [bookstore: bookstoreYangModelAsString])
+
}
def addBookstoreData() {
@@ -54,6 +65,12 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase {
addAnchorsWithData(NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DATA, FUNCTIONAL_TEST_DATASPACE_2, BOOKSTORE_SCHEMA_SET, 'bookstoreAnchor', bookstoreJsonData)
}
+ def addDeltaData() {
+ addAnchorsWithData(NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DELTA_DATA, FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_SCHEMA_SET, 'bookstoreSourceAnchor', bookstoreJsonData)
+ addAnchorsWithData(NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DELTA_DATA, FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_SCHEMA_SET, 'copyOfSourceAnchor', bookstoreJsonData)
+ addAnchorsWithData(NUMBER_OF_ANCHORS_PER_DATASPACE_WITH_BOOKSTORE_DELTA_DATA, FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_SCHEMA_SET, 'bookstoreAnchorForDeltaReport', bookstoreJsonDataForDeltaReport)
+ }
+
def restoreBookstoreDataAnchor(anchorNumber) {
def anchorName = 'bookstoreAnchor' + anchorNumber
cpsAdminService.deleteAnchor(FUNCTIONAL_TEST_DATASPACE_1, anchorName)
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 12c97ed40..017ede7de 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
@@ -32,6 +32,7 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.exceptions.DataspaceNotFoundException
+import org.onap.cps.spi.model.DeltaReport
import java.time.OffsetDateTime
@@ -432,6 +433,102 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
restoreBookstoreDataAnchor(2)
}
+ def 'Get delta between 2 anchors for when #scenario'() {
+ when: 'attempt to get delta report between anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, xpath, fetchDescendantOption)
+ then: 'delta report contains expected number of changes'
+ result.size() == 2
+ and: 'delta report contains expected action'
+ assert result.get(index).getAction() == expectedActions
+ and: 'delta report contains expected xpath'
+ assert result.get(index).getXpath() == expectedXpath
+ where: 'following data was used'
+ scenario | index | xpath || expectedActions || expectedXpath | fetchDescendantOption
+ 'a node is removed' | 0 | '/' || 'remove' || "/bookstore-address[@bookstore-name='Easons-1']" | OMIT_DESCENDANTS
+ 'a node is added' | 1 | '/' || 'add' || "/bookstore-address[@bookstore-name='Crossword Bookstores']" | OMIT_DESCENDANTS
+ }
+
+ def 'Get delta between 2 anchors where child nodes are added/removed but parent node remains unchanged'() {
+ def parentNodeXpath = "/bookstore"
+ when: 'attempt to get delta report between anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'delta report contains expected number of changes'
+ result.size() == 11
+ and: 'the delta report does not contain parent node xpath'
+ def xpaths = getDeltaReportEntities(result).get('xpaths')
+ assert !(xpaths.contains(parentNodeXpath))
+ }
+
+ def 'Get delta between 2 anchors returns empty response when #scenario'() {
+ when: 'attempt to get delta report between anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'delta report is empty'
+ assert result.isEmpty()
+ where: 'following data was used'
+ scenario | sourceAnchor | targetAnchor | xpath
+ 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_4 | '/'
+ 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_3 | '/'
+ 'non existing xpath' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath'
+ }
+
+ def 'Get delta between anchors error scenario: #scenario'() {
+ when: 'attempt to get delta between anchors'
+ objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchor, targetAnchor, '/some-xpath', INCLUDE_ALL_DESCENDANTS)
+ then: 'expected exception is thrown'
+ thrown(expectedException)
+ where: 'following data was used'
+ scenario | dataspaceName | sourceAnchor | targetAnchor || expectedException
+ 'invalid dataspace name' | 'Invalid dataspace' | 'not-relevant' | 'not-relevant' || DataValidationException
+ 'invalid anchor 1 name' | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor' | 'not-relevant' || DataValidationException
+ 'invalid anchor 2 name' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'invalid anchor' || DataValidationException
+ 'non-existing dataspace' | 'non-existing' | 'not-relevant1' | 'not-relevant2' || DataspaceNotFoundException
+ 'non-existing dataspace with same anchor name' | 'non-existing' | 'not-relevant' | 'not-relevant' || DataspaceNotFoundException
+ 'non-existing anchor 1' | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | 'not-relevant' || AnchorNotFoundException
+ 'non-existing anchor 2' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'non-existing-anchor' || AnchorNotFoundException
+ }
+
+ def 'Get delta between anchors for remove action, where source data node #scenario'() {
+ when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_5, BOOKSTORE_ANCHOR_3, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'expected action is present in delta report'
+ assert result.get(0).getAction() == 'remove'
+ where: 'following data was used'
+ scenario | parentNodeXpath
+ 'has leaves and child nodes' | "/bookstore/categories[@code='6']"
+ 'has leaves only' | "/bookstore/categories[@code='5']/books[@title='Book 11']"
+ 'has child data node only' | "/bookstore/support-info/contact-emails"
+ 'is empty' | "/bookstore/container-without-leaves"
+ }
+
+ def 'Get delta between anchors for add action, where target data node #scenario'() {
+ when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'the expected action is present in delta report'
+ result.get(0).getAction() == 'add'
+ and: 'the expected xapth is present in delta report'
+ result.get(0).getXpath() == parentNodeXpath
+ where: 'following data was used'
+ scenario | parentNodeXpath
+ 'has leaves and child nodes' | "/bookstore/categories[@code='6']"
+ 'has leaves only' | "/bookstore/categories[@code='5']/books[@title='Book 11']"
+ 'has child data node only' | "/bookstore/support-info/contact-emails"
+ 'is empty' | "/bookstore/container-without-leaves"
+ }
+
+ def getDeltaReportEntities(List<DeltaReport> deltaReport) {
+ def xpaths = []
+ def action = []
+ def sourcePayload = []
+ def targetPayload = []
+ deltaReport.each {
+ delta -> xpaths.add(delta.getXpath())
+ action.add(delta.getAction())
+ sourcePayload.add(delta.getSourceData())
+ targetPayload.add(delta.getTargetData())
+ }
+ return ['xpaths':xpaths, 'action':action, 'sourcePayload':sourcePayload, 'targetPayload':targetPayload]
+ }
+
def countDataNodesInBookstore() {
return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
index 8cdbdc595..d8012ec6d 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
@@ -67,7 +67,7 @@ class CpsPerfTestBase extends PerfTestBase {
addAnchorsWithData(OPENROADM_ANCHORS, CPS_PERFORMANCE_TEST_DATASPACE, LARGE_SCHEMA_SET, 'openroadm', data)
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
- recordAndAssertResourceUsage('Creating openroadm anchors with large data tree', 200, durationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Creating openroadm anchors with large data tree', 200, durationInSeconds, 600, resourceMeter.getTotalMemoryUsageInMB())
}
def generateOpenRoadData(numberOfNodes) {
@@ -87,7 +87,7 @@ class CpsPerfTestBase extends PerfTestBase {
then: 'memory used is within #peakMemoryUsage'
assert resourceMeter.getTotalMemoryUsageInMB() <= 30
and: 'all data is read within expected time'
- recordAndAssertResourceUsage("Warming database", 200, durationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Warming database", 200, durationInSeconds, 600, resourceMeter.getTotalMemoryUsageInMB())
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy
index b0a105c7f..a96d7f64b 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy
@@ -55,7 +55,7 @@ abstract class PerfTestBase extends CpsIntegrationSpecBase {
abstract def createInitialData()
def recordAndAssertResourceUsage(String shortTitle, double thresholdInSec, double recordedTimeInSec, memoryLimit, memoryUsageInMB) {
- def pass = recordedTimeInSec <= thresholdInSec
+ def pass = recordedTimeInSec <= thresholdInSec && memoryUsageInMB <= memoryLimit
if (shortTitle.length() > 40) {
shortTitle = shortTitle.substring(0, 40)
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy
index 6a3d4434b..ce66cb08a 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy
@@ -47,7 +47,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the operation completes within 25 seconds'
- recordAndAssertResourceUsage("Creating 33,000 books", 25, durationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Creating 33,000 books", 25, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Get data nodes from multiple xpaths 32K (2^15) limit exceeded.'() {
@@ -88,7 +88,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'test data is deleted in 1 second'
- recordAndAssertResourceUsage("Deleting test data", 1, durationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Deleting test data", 1, durationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def countDataNodes() {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy
index 0dcd995a7..818c300a5 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy
@@ -40,7 +40,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def setupDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'setup duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete test setup', 200, setupDurationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete test setup', 200, setupDurationInSeconds, 800, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 100 container nodes'() {
@@ -56,7 +56,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 containers', 2, deleteDurationInSeconds, 30, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 containers', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 container nodes'() {
@@ -70,7 +70,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 containers', 0.5, deleteDurationInSeconds, 5, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 containers', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 100 list elements'() {
@@ -86,7 +86,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 lists elements', 2, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 lists elements', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 list elements'() {
@@ -100,7 +100,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.5, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 100 whole lists'() {
@@ -116,7 +116,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 whole lists', 5, deleteDurationInSeconds, 30, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 whole lists', 6, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 whole lists'() {
@@ -130,7 +130,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 whole lists', 4, deleteDurationInSeconds, 5, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 whole lists', 5, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 1 large data node'() {
@@ -140,7 +140,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete one large node', 2, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete one large node', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete root node with many descendants'() {
@@ -150,7 +150,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete root node', 2, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete root node', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete data nodes for an anchor'() {
@@ -160,7 +160,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete data nodes for anchor', 2, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete data nodes for anchor', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 non-existing nodes'() {
@@ -174,7 +174,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 non-existing', 7, deleteDurationInSeconds, 5, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 non-existing', 7, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Clean up test data'() {
@@ -186,7 +186,7 @@ class DeletePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete test cleanup', 10, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete test cleanup', 10, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
index 95cf260f2..8a228a353 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
@@ -44,9 +44,9 @@ class GetPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Read datatrees with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'no descendants' | OMIT_DESCENDANTS || 0.01 | 5 | 1
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.05 | 10 | 1 + OPENROADM_DEVICES_PER_ANCHOR
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2 | 200 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'no descendants' | OMIT_DESCENDANTS || 0.02 | 1 | 1
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.06 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
def 'Read data trees for multiple xpaths'() {
@@ -60,7 +60,7 @@ class GetPerfTest extends CpsPerfTestBase {
then: 'requested nodes and their descendants are returned'
assert countDataNodesInTree(result) == OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
and: 'all data is read within expected time and memory used is within limit'
- recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 3 , durationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 4 , durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Read for multiple xpaths to non-existing datanodes'() {
@@ -74,7 +74,7 @@ class GetPerfTest extends CpsPerfTestBase {
then: 'no data is returned'
assert result.isEmpty()
and: 'the operation completes within within expected time'
- recordAndAssertResourceUsage("Read non-existing xpaths", 0.01, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Read non-existing xpaths", 0.02, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Read complete data trees using #scenario.'() {
@@ -88,9 +88,9 @@ class GetPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Read datatrees using ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following xpaths are used'
scenario | xpath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'openroadm root' | '/' || 2 | 200 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'openroadm top element' | '/openroadm-devices' || 2 | 200 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 3 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm root' | '/' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm top element' | '/openroadm-devices' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 3 | 250 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
index 5cf495563..0ae018d46 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
@@ -45,10 +45,11 @@ class QueryPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Query 1 anchor ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'top element' | '/openroadm-devices' || 2 | 300 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
- 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 3 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 2 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
- 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 2 | 300 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'top element' | '/openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.1 | 1 | 0
}
def 'Query complete data trees across all anchors with #scenario.'() {
@@ -63,11 +64,10 @@ class QueryPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Query across anchors ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpspath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'top element' | '/openroadm-devices' || 6 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
- 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 6 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE)
- 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 6 | 800 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
- 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 6 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
- 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.1 | 3 | 0
+ 'top element' | '/openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
+ 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE)
+ 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
+ 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
}
def 'Query with leaf condition and #scenario.'() {
@@ -82,9 +82,9 @@ class QueryPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Query with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'no descendants' | OMIT_DESCENDANTS || 0.1 | 30 | OPENROADM_DEVICES_PER_ANCHOR
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.15 | 30 | OPENROADM_DEVICES_PER_ANCHOR * 2
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'no descendants' | OMIT_DESCENDANTS || 0.1 | 6 | OPENROADM_DEVICES_PER_ANCHOR
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
def 'Query ancestors with #scenario.'() {
@@ -99,9 +99,9 @@ class QueryPerfTest extends CpsPerfTestBase {
recordAndAssertResourceUsage("Query ancestors with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'no descendants' | OMIT_DESCENDANTS || 0.1 | 20 | 1
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.1 | 20 | 1 + OPENROADM_DEVICES_PER_ANCHOR
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2 | 200 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'no descendants' | OMIT_DESCENDANTS || 0.1 | 3 | 1
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy
index 151492dc9..8118010b4 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy
@@ -41,7 +41,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def updateDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'update completes within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Update 1 data node', 0.6, updateDurationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Update 1 data node', 0.6, updateDurationInSeconds, 100, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch update 100 data nodes with descendants'() {
@@ -57,7 +57,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def updateDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'update completes within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Update 100 data nodes', 30, updateDurationInSeconds, 800, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Update 100 data nodes', 40, updateDurationInSeconds, 800, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Update leaves for 1 data node (twice)'() {
@@ -71,7 +71,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def updateDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'update completes within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Update leaves for 1 data node', 0.5, updateDurationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Update leaves for 1 data node', 0.7, updateDurationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch update leaves for 100 data nodes (twice)'() {
@@ -85,7 +85,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def updateDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'update completes within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch update leaves for 100 data nodes', 1, updateDurationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch update leaves for 100 data nodes', 1, updateDurationInSeconds, 200, resourceMeter.getTotalMemoryUsageInMB())
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy
index 177cd9fc5..0c7107a56 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy
@@ -36,19 +36,16 @@ class WritePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the operation takes less than #expectedDuration and memory used is within limit'
- recordAndAssertResourceUsage("Writing ${totalNodes} devices", expectedDurationInSeconds, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Writing ${totalNodes} devices", expectedDuration, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
cleanup:
cpsDataService.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor', OffsetDateTime.now())
cpsAdminService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor')
where:
- totalNodes || expectedDurationInSeconds
- 50 || 3
- 100 || 5
- 200 || 10
- 400 || 20
-// 800 || 40
-// 1600 || 80
-// 3200 || 160
+ totalNodes || expectedDuration | memoryLimit
+ 50 || 4 | 100
+ 100 || 7 | 200
+ 200 || 14 | 400
+ 400 || 28 | 500
}
def 'Writing bookstore data has exponential time.'() {
@@ -64,20 +61,16 @@ class WritePerfTest extends CpsPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the operation takes less than #expectedDuration and memory used is within limit'
- recordAndAssertResourceUsage("Writing ${totalBooks} books", expectedDuration, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Writing ${totalBooks} books", expectedDuration, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
cleanup:
cpsDataService.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor', OffsetDateTime.now())
cpsAdminService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor')
where:
- totalBooks || expectedDuration
- 400 || 0.2
- 800 || 0.5
- 1600 || 1
- 3200 || 3
- 6400 || 10
-// 12800 || 30
-// 25600 || 120
-// 51200 || 600
+ totalBooks || expectedDuration | memoryLimit
+ 800 || 1 | 50
+ 1600 || 2 | 100
+ 3200 || 6 | 150
+ 6400 || 18 | 200
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy
index 896217a2a..579394be8 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy
@@ -52,7 +52,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase {
matches.size() == numberOfFiltersPerCmHandle * numberOfCmHandlesPerCmDataSubscription
and: 'query all subscribers within 1 second'
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
- recordAndAssertResourceUsage("Query all subscribers", 1, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Query all subscribers", 1.2, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Worst case subscription update (200x10 matching entries).'() {
@@ -94,8 +94,8 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase {
then: 'a subscriber has been added to each filter entry'
def resultAfter = objectUnderTest.queryDataNodes(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, cpsPath, INCLUDE_ALL_DESCENDANTS)
assert resultAfter.collect {it.leaves.subscribers.size()}.sum() == totalNumberOfEntries * (1 + numberOfCmDataSubscribers)
- and: 'update matching subscription within 8 seconds'
- recordAndAssertResourceUsage("Update matching subscription", 8, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
+ and: 'update matching subscription within 15 seconds'
+ recordAndAssertResourceUsage("Update matching subscription", 15, durationInSeconds, 1000, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Worst case new subscription (200x10 new entries).'() {
@@ -109,7 +109,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'insert new subscription with 1 second'
- recordAndAssertResourceUsage("Insert new subscription", 1, durationInSeconds, 400,resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Insert new subscription", 2, durationInSeconds, 100, resourceMeter.getTotalMemoryUsageInMB())
}
def querySubscriptionsByIteration(Collection<DataNode> allSubscriptionsAsDataNodes, targetSubscriptionSequenceNumber) {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
index becd7e39c..a5a6acb7a 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
@@ -45,8 +45,8 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase {
def result = cpsDataService.getDataNodesForMultipleXpaths(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, xpaths, INCLUDE_ALL_DESCENDANTS)
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
- then: 'the required operations are performed within 1200 ms'
- recordAndAssertResourceUsage("CpsPath Registry attributes Query", 0.25, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB())
+ then: 'the required operations are performed within required time'
+ recordAndAssertResourceUsage("CpsPath Registry attributes Query", 0.4, durationInSeconds, 50, resourceMeter.getTotalMemoryUsageInMB())
and: 'all but 1 (other node) are returned'
result.size() == 999
and: 'the tree contains all the expected descendants too'
diff --git a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java
index 1e420013d..f8a2ecb4d 100644
--- a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java
+++ b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java
@@ -20,6 +20,10 @@
package org.onap.cps.integration;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryType;
import org.springframework.util.StopWatch;
/**
@@ -34,8 +38,9 @@ public class ResourceMeter {
* Start measurement.
*/
public void start() {
- System.gc();
- memoryUsedBefore = getCurrentMemoryUsage();
+ performGcAndWait();
+ resetPeakHeapUsage();
+ memoryUsedBefore = getPeakHeapUsage();
stopWatch.start();
}
@@ -44,12 +49,12 @@ public class ResourceMeter {
*/
public void stop() {
stopWatch.stop();
- memoryUsedAfter = getCurrentMemoryUsage();
+ memoryUsedAfter = getPeakHeapUsage();
}
/**
- * Get the total time in milliseconds.
- * @return total time in milliseconds
+ * Get the total time in seconds.
+ * @return total time in seconds
*/
public double getTotalTimeInSeconds() {
return stopWatch.getTotalTimeSeconds();
@@ -63,8 +68,30 @@ public class ResourceMeter {
return (memoryUsedAfter - memoryUsedBefore) / 1_000_000.0;
}
- private static long getCurrentMemoryUsage() {
- return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ static void performGcAndWait() {
+ final long gcCountBefore = getGcCount();
+ System.gc();
+ while (getGcCount() == gcCountBefore) {}
+ }
+
+ private static long getGcCount() {
+ return ManagementFactory.getGarbageCollectorMXBeans().stream()
+ .mapToLong(GarbageCollectorMXBean::getCollectionCount)
+ .filter(gcCount -> gcCount != -1)
+ .sum();
+ }
+
+ private static long getPeakHeapUsage() {
+ return ManagementFactory.getMemoryPoolMXBeans().stream()
+ .filter(pool -> pool.getType() == MemoryType.HEAP)
+ .mapToLong(pool -> pool.getPeakUsage().getUsed())
+ .sum();
+ }
+
+ private static void resetPeakHeapUsage() {
+ ManagementFactory.getMemoryPoolMXBeans().stream()
+ .filter(pool -> pool.getType() == MemoryType.HEAP)
+ .forEach(MemoryPoolMXBean::resetPeakUsage);
}
}
diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang
index 6f60f1981..9c6c42e28 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstore.yang
+++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang
@@ -49,6 +49,17 @@ module stores {
}
}
+ container support-info {
+ leaf support-office {
+ type string;
+ }
+ container contact-emails {
+ leaf email {
+ type string;
+ }
+ }
+ }
+
container container-without-leaves { }
container premises {
diff --git a/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json
new file mode 100644
index 000000000..73b84fc98
--- /dev/null
+++ b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json
@@ -0,0 +1,192 @@
+{
+ "bookstore-address": [
+ {
+ "bookstore-name": "Crossword Bookstores",
+ "address": "Bangalore, India",
+ "postal-code": "560062"
+ }
+ ],
+ "bookstore": {
+ "bookstore-name": "Easons",
+ "premises": {
+ "addresses": [
+ {
+ "house-number": 2,
+ "street": "Main Street",
+ "town": "Killarney",
+ "county": "Kerry"
+ },
+ {
+ "house-number": 24,
+ "street": "Grafton Street",
+ "town": "Dublin",
+ "county": "Dublin"
+ }
+ ]
+ },
+ "support-info": {
+ "contact-emails": {
+ }
+ },
+ "container-without-leaves": { },
+ "categories": [
+ {
+ "code": 1,
+ "name": "Kids",
+ "books" : [
+ {
+ "title": "Matilda",
+ "lang": "English",
+ "authors": ["Roald Dahl"],
+ "editions": [1988, 2000, 2023],
+ "price": 200
+ },
+ {
+ "title": "The Gruffalo",
+ "lang": "English/German",
+ "authors": ["Julia Donaldson"],
+ "editions": [1999],
+ "price": 15
+ }
+ ]
+ },
+ {
+ "code": 2,
+ "name": "Suspense"
+ },
+ {
+ "code": 3,
+ "name": "Comedy",
+ "books" : [
+ {
+ "title": "Good Omens",
+ "lang": "English",
+ "authors": ["Neil Gaiman", "Terry Pratchett"],
+ "editions": [2006],
+ "price": 13
+ },
+ {
+ "title": "The Colour of Magic",
+ "lang": "English",
+ "authors": ["Terry Pratchett"],
+ "editions": [1983],
+ "price": 12
+ },
+ {
+ "title": "The Light Fantastic",
+ "lang": "English",
+ "authors": ["Terry Pratchett"],
+ "editions": [1986],
+ "price": 14
+ },
+ {
+ "title": "A Book with No Language",
+ "lang": "",
+ "authors": ["Joe Bloggs"],
+ "editions": [2023],
+ "price": 20
+ }
+ ]
+ },
+ {
+ "code": 5,
+ "name": "Discount books",
+ "books" : [
+ {
+ "title": "Book 1",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 1
+ },
+ {
+ "title": "Book 2",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 2
+ },
+ {
+ "title": "Book 3",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 3
+ },
+ {
+ "title": "Book 4",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 4
+ },
+ {
+ "title": "Book 5",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 5
+ },
+ {
+ "title": "Book 6",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 6
+ },
+ {
+ "title": "Book 7",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 7
+ },
+ {
+ "title": "Book 8",
+ "lang": "blahh",
+ "authors": [],
+ "editions": [],
+ "price": 8
+ },
+ {
+ "title": "Book 9",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 9
+ },
+ {
+ "title": "Book 10",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 10
+ },
+ {
+ "title": "Book 11",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 10
+ }
+ ]
+ },
+ {
+ "code": 6,
+ "name": "Random books",
+ "books": [
+ {
+ "title": "Book 1",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 1
+ }
+ ]
+ },
+ {
+ "code": 7
+ }
+ ]
+ }
+}