diff options
Diffstat (limited to 'cps-ncmp-service')
180 files changed, 4467 insertions, 2473 deletions
diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index fc41da3ae..1f94b34ea 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.0-SNAPSHOT</version> + <version>3.5.1-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -38,6 +38,22 @@ </properties> <dependencies> <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-exporter-otlp</artifactId> + </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk-extension-jaeger-remote-sampler</artifactId> + </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId> + </dependency> + <dependency> + <groupId>io.opentelemetry.instrumentation</groupId> + <artifactId>opentelemetry-kafka-clients-2.6</artifactId> + </dependency> + <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> @@ -58,6 +74,10 @@ <artifactId>cps-path-parser</artifactId> </dependency> <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>annotations</artifactId> + </dependency> + <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-spring</artifactId> </dependency> @@ -70,8 +90,8 @@ <artifactId>mapstruct-processor</artifactId> </dependency> <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-web</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- T E S T - D E P E N D E N C I E S --> <dependency> @@ -104,5 +124,13 @@ <artifactId>spock</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator-autoconfigure</artifactId> + </dependency> + <dependency> + <groupId>jakarta.servlet</groupId> + <artifactId>jakarta.servlet-api</artifactId> + </dependency> </dependencies> </project> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java index bdc3dee77..8cfad7dbf 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java @@ -26,14 +26,12 @@ import lombok.Getter; public enum NcmpResponseStatus { SUCCESS("0", "Successfully applied changes"), - SUCCESSFULLY_APPLIED_SUBSCRIPTION("1", "successfully applied subscription"), + CM_DATA_SUBSCRIPTION_ACCEPTED("1", "ACCEPTED"), CM_HANDLES_NOT_FOUND("100", "cm handle id(s) not found"), CM_HANDLES_NOT_READY("101", "cm handle(s) not ready"), DMI_SERVICE_NOT_RESPONDING("102", "dmi plugin service is not responding"), UNABLE_TO_READ_RESOURCE_DATA("103", "dmi plugin service is not able to read resource data"), - PARTIALLY_APPLIED_SUBSCRIPTION("104", "partially applied subscription"), - SUBSCRIPTION_NOT_APPLICABLE("105", "subscription not applicable for all cm handles"), - SUBSCRIPTION_PENDING("106", "subscription pending for all cm handles"), + CM_DATA_SUBSCRIPTION_REJECTED("104", "REJECTED"), UNKNOWN_ERROR("108", "Unknown error"), CM_HANDLE_ALREADY_EXIST("109", "cm-handle already exists"), CM_HANDLE_INVALID_ID("110", "cm-handle has an invalid character(s) in id"), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java deleted file mode 100644 index 20545d711..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 highstreet technologies GmbH - * Modifications Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * 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; - -import java.util.Collection; -import java.util.Map; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.operations.OperationType; -import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.models.CmResourceAddress; -import org.onap.cps.ncmp.api.models.DataOperationRequest; -import org.onap.cps.ncmp.api.models.DmiPluginRegistration; -import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; -import org.onap.cps.spi.FetchDescendantsOption; -import org.onap.cps.spi.model.ModuleDefinition; -import org.onap.cps.spi.model.ModuleReference; - -/* - * Datastore interface for handling CPS data. - */ -public interface NetworkCmProxyDataService { - - /** - * Registration of New CM Handles. - * - * @param dmiPluginRegistration Dmi Plugin Registration - * @return dmiPluginRegistrationResponse - */ - DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration); - - /** - * Get resource data for given data store using dmi. - * - * @param cmResourceAddress target datastore, cm handle and resource identifier - * @param optionsParamInQuery options query - * @param topicParamInQuery topic name for (triggering) async responses - * @param requestId unique requestId for async request - * @param authorization contents of Authorization header, or null if not present - * @return {@code Object} resource data - */ - Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress, - String optionsParamInQuery, - String topicParamInQuery, - String requestId, - String authorization); - - /** - * Get resource data for operational. - * - * @param cmResourceAddress target datastore, cm handle and resource identifier - * @Link FetchDescendantsOption fetch descendants option - * @return {@code Object} resource data - */ - Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress, - FetchDescendantsOption fetchDescendantsOption); - - /** - * Execute (async) data operation for group of cm handles using dmi. - * - * @param topicParamInQuery topic name for (triggering) async responses - * @param dataOperationRequest contains a list of operation definitions(multiple operations) - * @param requestId request ID - * @param authorization contents of Authorization header, or null if not present - */ - void executeDataOperationForCmHandles(String topicParamInQuery, - DataOperationRequest dataOperationRequest, - String requestId, - String authorization); - - - /** - * Write resource data for data store pass-through running using dmi for given cm-handle. - * - * @param cmHandleId cm handle identifier - * @param resourceIdentifier resource identifier - * @param operationType required operation type - * @param requestBody request body to create resource - * @param contentType content type in body - * @param authorization contents of Authorization header, or null if not present - * @return {@code Object} return data - */ - Object writeResourceDataPassThroughRunningForCmHandle(String cmHandleId, - String resourceIdentifier, - OperationType operationType, - String requestBody, - String contentType, - String authorization); - - /** - * Retrieve module references for the given cm handle. - * - * @param cmHandleId cm handle identifier - * @return a collection of modules names and revisions - */ - Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId); - - /** - * Retrieve module definitions for the given cm handle. - * - * @param cmHandleId cm handle identifier - * @return a collection of module definition (moduleName, revision and yang resource content) - */ - Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId); - - /** - * Get module definitions for the given parameters. - * - * @param cmHandleId cm-handle identifier - * @param moduleName module name - * @param moduleRevision the revision of the module - * @return list of module definitions (module name, revision, yang resource content) - */ - Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(String cmHandleId, - String moduleName, - String moduleRevision); - - /** - * Query cm handle details by cm handle's name. - * - * @param cmHandleId cm handle identifier - * @return a collection of cm handle details. - */ - NcmpServiceCmHandle getNcmpServiceCmHandle(String cmHandleId); - - /** - * Get cm handle public properties by cm handle id. - * - * @param cmHandleId cm handle identifier - * @return a collection of cm handle public properties. - */ - Map<String, String> getCmHandlePublicProperties(String cmHandleId); - - /** - * Get cm handle composite state by cm handle id. - * - * @param cmHandleId cm handle identifier - * @return a cm handle composite state - */ - CompositeState getCmHandleCompositeState(String cmHandleId); - - /** - * Query and return cm handles that match the given query parameters. - * - * @param cmHandleQueryApiParameters the cm handle query parameters - * @return collection of cm handles - */ - Collection<NcmpServiceCmHandle> executeCmHandleSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters); - - /** - * Query and return cm handle ids that match the given query parameters. - * - * @param cmHandleQueryApiParameters the cm handle query parameters - * @return collection of cm handle ids - */ - Collection<String> executeCmHandleIdSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters); - - /** - * Set the data sync enabled flag, along with the data sync state to true or false based on the cm handle id. - * - * @param cmHandleId cm handle id - * @param dataSyncEnabled data sync enabled flag - */ - void setDataSyncEnabled(String cmHandleId, Boolean dataSyncEnabled); - - /** - * Get all cm handle IDs by DMI plugin identifier. - * - * @param dmiPluginIdentifier DMI plugin identifier - * @return collection of cm handle IDs - */ - Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier); - - /** - * Get all cm handle IDs by various search criteria. - * - * @param cmHandleQueryServiceParameters cm handle query parameters - * @return collection of cm handle IDs - */ - Collection<String> executeCmHandleIdSearchForInventory(CmHandleQueryServiceParameters - cmHandleQueryServiceParameters); -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java index 340806b89..39d497217 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,9 @@ package org.onap.cps.ncmp.api; +import java.util.Collection; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.model.DataNode; /* * Datastore interface for handling cached CPS data query requests. @@ -28,14 +30,21 @@ import org.onap.cps.spi.FetchDescendantsOption; public interface NetworkCmProxyQueryService { /** - * Get resource data for operational. + * Fetches operational resource data based on the provided CM handle identifier and CPS path. + * This method retrieves data nodes from the specified path within the context of a given CM handle. + * It supports options for fetching descendant nodes. * - * @param cmHandleId cm handle identifier - * @param cpsPath cps path - * @Link FetchDescendantsOption fetch descendants option - * @return {@code Object} resource data + * @param cmHandleId The CM handle identifier, which uniquely identifies the CM handle. + * This parameter must not be null. + * @param cpsPath The CPS (Control Plane Service) path specifying the location of the + * resource data within the CM handle. This parameter must not be null. + * @param fetchDescendantsOption The option specifying whether to fetch descendant nodes along with the specified + * resource data. + * @return {@code Collection<DataNode>} A collection of DataNode objects representing the resource data + * retrieved from the specified path. The collection may include descendant nodes based on the + * fetchDescendantsOption. */ - Object queryResourceDataOperational(String cmHandleId, - String cpsPath, - FetchDescendantsOption fetchDescendantsOption); + Collection<DataNode> queryResourceDataOperational(String cmHandleId, + String cpsPath, + FetchDescendantsOption fetchDescendantsOption); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/DataJobService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobService.java index 6122afc80..6ff79a934 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/DataJobService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobService.java @@ -18,11 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api; +package org.onap.cps.ncmp.api.datajobs; -import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata; -import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest; -import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest; +import java.util.List; +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata; +import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest; +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest; +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse; public interface DataJobService { @@ -41,6 +43,8 @@ public interface DataJobService { * @param dataJobId Unique identifier of the job within the request * @param dataJobMetadata data job request headers * @param dataJobWriteRequest write data job request + * @return a list of sub-job write responses */ - void writeDataJob(String dataJobId, DataJobMetadata dataJobMetadata, DataJobWriteRequest dataJobWriteRequest); + List<SubJobWriteResponse> writeDataJob(String dataJobId, DataJobMetadata dataJobMetadata, + DataJobWriteRequest dataJobWriteRequest); }
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobMetadata.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobMetadata.java index dc8037b86..564352d8d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobMetadata.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobMetadata.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models.datajob; +package org.onap.cps.ncmp.api.datajobs.models; /** * Metadata of read/write data job request. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobReadRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobReadRequest.java index f861c3d49..19408b1da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobReadRequest.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobReadRequest.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models.datajob; +package org.onap.cps.ncmp.api.datajobs.models; import java.util.List; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobWriteRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobWriteRequest.java index 254e198b8..d8961b17c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobWriteRequest.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobWriteRequest.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models.datajob; +package org.onap.cps.ncmp.api.datajobs.models; import java.util.List; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java new file mode 100644 index 000000000..7e9ca7988 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.datajobs.models; + +import java.util.Map; + +/** + * Describes the write data job operation to be forwarded to dmi. + * + * @param path Identifier of a managed object (MO) on a network element. Defines the resource on which + * operation is executed. Typically, is Fully Distinguished Name (FDN). + * @param op Describes the operation to execute. The value can be as below: + * e.g. "add", "replace", "remove", "action" etc. + * @param moduleSetTag The module set tag of the CM Handle. + * @param value The value to be written depends on the type of operation. + * @param operationId Unique identifier of the operation within the request. + * @param privateProperties Contains the private properties of a Cm Handle. + */ +public record DmiWriteOperation( + String path, + String op, + String moduleSetTag, + Object value, + String operationId, + Map<String, String> privateProperties) {} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java new file mode 100644 index 000000000..ac6b7f862 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java @@ -0,0 +1,36 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.datajobs.models; + +/** + * Composite key created from the DMI Service name and a data producer identifier. + * Helps to group of the sub job request for a given DMI Plugin. + * + * @param dmiServiceName Describes the name of the relevant DMI service. + * @param dataProducerIdentifier The name of a data producer identifier from a Cm Handle. + */ +public record ProducerKey(String dmiServiceName, String dataProducerIdentifier) { + + @Override + public String toString() { + return dmiServiceName + "#" + dataProducerIdentifier; + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/ReadOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ReadOperation.java index d2b073896..2459e4cc2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/ReadOperation.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ReadOperation.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models.datajob; +package org.onap.cps.ncmp.api.datajobs.models; import java.util.List; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java new file mode 100644 index 000000000..432b21b8c --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java @@ -0,0 +1,41 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.datajobs.models; + +import java.util.Collection; + +/** + * Response data for a write operation by the DMI Plugin. + * + * @param dataAcceptType Define the data response accept type. + * e.g. "application/vnd.3gpp.object-tree-hierarchical+json", + * "application/vnd.3gpp.object-tree-flat+json" etc. + * @param dataContentType Define the data request content type. + * e.g. "application/3gpp-json-patch+json" etc. + * @param dataProducerId Identifier of the data producer. + * + * @param data A collection of outgoing write operations. + */ +public record SubJobWriteRequest ( + String dataAcceptType, + String dataContentType, + String dataProducerId, + Collection<DmiWriteOperation> data) {}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java new file mode 100644 index 000000000..9cdd8e059 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java @@ -0,0 +1,30 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.datajobs.models; + +/** + * Request data for a write operation towards DMI Plugin. + * + * @param subJobId Identifier of the sub-job from DMI. + * @param dmiServiceName The provided name of the DMI service from the request. + * @param dataProducerId Identifier of the data producer. + */ +public record SubJobWriteResponse(String subJobId, String dmiServiceName, String dataProducerId) {}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/WriteOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/WriteOperation.java index c2f6504ce..807e03f06 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/WriteOperation.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/WriteOperation.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models.datajob; +package org.onap.cps.ncmp.api.datajobs.models; /** * Holds information of write data job operation. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandler.java new file mode 100644 index 000000000..da230cf73 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandler.java @@ -0,0 +1,76 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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; + +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.ncmp.api.NetworkCmProxyQueryService; +import org.onap.cps.ncmp.api.models.CmResourceAddress; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.model.DataNode; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandler { + + private final CpsDataService cpsDataService; + private final NetworkCmProxyQueryService networkCmProxyQueryService; + + /** + * Executes a synchronous query request for given cm handle. + * Note. Currently only ncmp-datastore:operational supports query operations. + * + * @param cmHandleId the cm handle + * @param resourceIdentifier the resource identifier + * @param includeDescendants whether include descendants + * @return a collection of data nodes + */ + public Collection<DataNode> executeRequest(final String cmHandleId, final String resourceIdentifier, + final boolean includeDescendants) { + final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants); + return networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier, + fetchDescendantsOption); + } + + @Override + protected Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendants, + final String authorization) { + final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants); + + final DataNode dataNode = cpsDataService.getDataNodes(cmResourceAddress.datastoreName(), + cmResourceAddress.cmHandleId(), + cmResourceAddress.resourceIdentifier(), + fetchDescendantsOption).iterator().next(); + return Mono.justOrEmpty(dataNode); + } + + private static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) { + return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + : FetchDescendantsOption.OMIT_DESCENDANTS; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandler.java new file mode 100644 index 000000000..302ba449c --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandler.java @@ -0,0 +1,96 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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; + +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.models.CmResourceAddress; +import org.onap.cps.ncmp.utils.TopicValidator; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Slf4j +@Service +public abstract class NcmpDatastoreRequestHandler { + + private static final String NO_REQUEST_ID = null; + private static final String NO_TOPIC = null; + + @Value("${notification.async.executor.time-out-value-in-ms:60000}") + protected int timeOutInMilliSeconds; + @Value("${notification.enabled:true}") + protected boolean notificationFeatureEnabled; + + /** + * Executes synchronous/asynchronous get request for given cm handle. + * + * @param cmResourceAddress the name of the datastore, cm handle and resource identifier + * @param options options to pass through to dmi client + * @param topic topic (optional) for asynchronous responses + * @param includeDescendants whether include descendants + * @param authorization contents of Authorization header, or null if not present + * @return the result object, depends on use op topic. With topic a map object with request id is returned + * otherwise the result of the request. + */ + public Object executeRequest(final CmResourceAddress cmResourceAddress, + final String options, + final String topic, + final boolean includeDescendants, + final String authorization) { + + final boolean asyncResponseRequested = topic != null; + if (asyncResponseRequested && notificationFeatureEnabled) { + return getResourceDataAsynchronously(cmResourceAddress, options, topic, includeDescendants, authorization); + } + + if (asyncResponseRequested) { + log.warn("Asynchronous request is unavailable as notification feature is currently disabled, " + + "will use synchronous operation."); + } + final Mono<Object> resourceDataMono = getResourceDataForCmHandle(cmResourceAddress, options, + NO_TOPIC, NO_REQUEST_ID, includeDescendants, authorization); + return resourceDataMono.block(); + } + + private Map<String, String> getResourceDataAsynchronously(final CmResourceAddress cmResourceAddress, + final String options, + final String topic, + final boolean includeDescendants, + final String authorization) { + TopicValidator.validateTopicName(topic); + final String requestId = UUID.randomUUID().toString(); + getResourceDataForCmHandle(cmResourceAddress, options, topic, requestId, includeDescendants, authorization) + .doOnSuccess(result -> + log.debug("Async operation succeeded for request id {}: {}", requestId, result)) + .subscribe(); + log.debug("Received Async request with id {}", requestId); + return Map.of("requestId", requestId); + } + + protected abstract Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendant, + final String authorization); +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpPassthroughResourceRequestHandler.java new file mode 100644 index 000000000..0fd32501c --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NcmpPassthroughResourceRequestHandler.java @@ -0,0 +1,103 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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; + +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ; + +import java.util.Map; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException; +import org.onap.cps.ncmp.api.impl.operations.DatastoreType; +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; +import org.onap.cps.ncmp.api.impl.operations.OperationType; +import org.onap.cps.ncmp.api.models.CmResourceAddress; +import org.onap.cps.ncmp.api.models.DataOperationRequest; +import org.onap.cps.ncmp.exceptions.OperationNotSupportedException; +import org.onap.cps.ncmp.exceptions.PayloadTooLargeException; +import org.onap.cps.ncmp.utils.TopicValidator; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestHandler { + + private final DmiDataOperations dmiDataOperations; + + private static final int MAXIMUM_CM_HANDLES_PER_OPERATION = 200; + private static final String PAYLOAD_TOO_LARGE_TEMPLATE = "Operation '%s' affects too many (%d) cm handles"; + + /** + * Executes asynchronous request for group of cm handles to resource data. + * + * @param topic the topic param in query + * @param dataOperationRequest data operation request details for resource data + * @param authorization contents of Authorization header, or null if not present + * @return a map with one entry of request Id for success or status and error when async feature is disabled + */ + public Map<String, String> executeAsynchronousRequest(final String topic, + final DataOperationRequest dataOperationRequest, + final String authorization) { + validateDataOperationRequest(topic, dataOperationRequest); + if (!notificationFeatureEnabled) { + return Map.of("status", + "Asynchronous request is unavailable as notification feature is currently disabled."); + } + final String requestId = UUID.randomUUID().toString(); + dmiDataOperations.requestResourceDataFromDmi(topic, dataOperationRequest, requestId, authorization); + return Map.of("requestId", requestId); + } + + @Override + protected Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, + final String options, + final String topic, + final String requestId, + final boolean includeDescendants, + final String authorization) { + + return dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, options, topic, requestId, authorization) + .flatMap(responseEntity -> Mono.justOrEmpty(responseEntity.getBody())); + } + + private void validateDataOperationRequest(final String topicParamInQuery, + final DataOperationRequest dataOperationRequest) { + TopicValidator.validateTopicName(topicParamInQuery); + dataOperationRequest.getDataOperationDefinitions().forEach(dataOperationDefinition -> { + if (OperationType.fromOperationName(dataOperationDefinition.getOperation()) != READ) { + throw new OperationNotSupportedException( + dataOperationDefinition.getOperation() + " operation not yet supported"); + } + if (DatastoreType.fromDatastoreName(dataOperationDefinition.getDatastore()) == OPERATIONAL) { + throw new InvalidDatastoreException(dataOperationDefinition.getDatastore() + + " datastore is not supported"); + } + if (dataOperationDefinition.getCmHandleIds().size() > MAXIMUM_CM_HANDLES_PER_OPERATION) { + final String errorMessage = String.format(PAYLOAD_TOO_LARGE_TEMPLATE, + dataOperationDefinition.getOperationId(), + dataOperationDefinition.getCmHandleIds().size()); + throw new PayloadTooLargeException(errorMessage); + } + }); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacade.java new file mode 100644 index 000000000..a24b18446 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacade.java @@ -0,0 +1,129 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 highstreet technologies GmbH + * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl; + +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL; + +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.operations.DatastoreType; +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; +import org.onap.cps.ncmp.api.impl.operations.OperationType; +import org.onap.cps.ncmp.api.models.CmResourceAddress; +import org.onap.cps.ncmp.api.models.DataOperationRequest; +import org.onap.cps.spi.model.DataNode; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class NetworkCmProxyFacade { + + private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; + private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; + private final DmiDataOperations dmiDataOperations; + + /** + * Fetches resource data for a given data store using DMI (Data Management Interface). + * This method retrieves data based on the provided CmResourceAddress and additional query parameters. + * It supports asynchronous processing and handles authorization if required. + * + * @param cmResourceAddress The target data store, including the CM handle and resource identifier. + * This parameter must not be null. + * @param options Additional query parameters that may influence the data retrieval process, + * such as filters or limits. This parameter can be null. + * @param topic The topic name for triggering asynchronous responses. If specified, + * the response will be sent to this topic. This parameter can be null. + * @param includeDescendants include (all) descendants or not + * @param authorization The contents of the Authorization header. This parameter can be null + * if authorization is not required. + * @return the result object, depends on use op topic. With topic a map object with request id is returned + * otherwise the result of the request. + */ + public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, + final String options, + final String topic, + final Boolean includeDescendants, + final String authorization) { + final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler + = getNcmpDatastoreRequestHandler(cmResourceAddress.datastoreName()); + + return ncmpDatastoreRequestHandler.executeRequest(cmResourceAddress, options, topic, includeDescendants, + authorization); + } + + /** + * Executes asynchronous request for group of cm handles to resource data. + * + * @param topic the topic param in query + * @param dataOperationRequest data operation request details for resource data + * @param authorization contents of Authorization header, or null if not present + * @return a map with one entry of request Id for success or status and error when async feature is disabled + */ + public Object executeDataOperationForCmHandles(final String topic, + final DataOperationRequest dataOperationRequest, + final String authorization) { + return ncmpPassthroughResourceRequestHandler.executeAsynchronousRequest(topic, + dataOperationRequest, + authorization); + } + + public Collection<DataNode> queryResourceDataForCmHandle(final String cmHandle, + final String cpsPath, + final Boolean includeDescendants) { + return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants); + } + + /** + * Write resource data for data store pass-through running using dmi for given cm-handle. + * + * @param cmHandleId cm handle identifier + * @param resourceIdentifier resource identifier + * @param operationType required operation type + * @param requestData request body to create resource + * @param dataType content type in body + * @param authorization contents of Authorization header, or null if not present + * @return {@code Object} return data + */ + public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId, + final String resourceIdentifier, + final OperationType operationType, + final String requestData, + final String dataType, + final String authorization) { + return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, + operationType, requestData, dataType, authorization); + } + + + private NcmpDatastoreRequestHandler getNcmpDatastoreRequestHandler(final String datastoreName) { + if (OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) { + return ncmpCachedResourceRequestHandler; + } + return ncmpPassthroughResourceRequestHandler; + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImpl.java index d8353f302..8507fb773 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImpl.java @@ -20,13 +20,15 @@ package org.onap.cps.ncmp.api.impl; -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import java.util.Collection; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsQueryService; import org.onap.cps.ncmp.api.NetworkCmProxyQueryService; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; @Slf4j @@ -37,9 +39,9 @@ public class NetworkCmProxyQueryServiceImpl implements NetworkCmProxyQueryServic private final CpsQueryService cpsQueryService; @Override - public Object queryResourceDataOperational(final String cmHandleId, - final String cpsPath, - final FetchDescendantsOption fetchDescendantsOption) { + public Collection<DataNode> queryResourceDataOperational(final String cmHandleId, + final String cpsPath, + final FetchDescendantsOption fetchDescendantsOption) { return cpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cpsPath, fetchDescendantsOption); } 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 798a280c8..d4c5d16a4 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,20 +21,36 @@ package org.onap.cps.ncmp.api.impl.client; +import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING; +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA; +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.REQUEST_TIMEOUT; + import com.fasterxml.jackson.databind.JsonNode; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Locale; 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.config.DmiProperties; +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException; +import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException; import org.onap.cps.ncmp.api.impl.operations.OperationType; -import org.springframework.http.HttpEntity; +import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; @Component @RequiredArgsConstructor @@ -43,60 +59,136 @@ public class DmiRestClient { private static final String HEALTH_CHECK_URL_EXTENSION = "/actuator/health"; private static final String NOT_SPECIFIED = ""; - private final RestTemplate restTemplate; + private static final String NO_AUTHORIZATION = null; + private final DmiProperties dmiProperties; + private final JsonObjectMapper jsonObjectMapper; + @Qualifier("dataServicesWebClient") + private final WebClient dataServicesWebClient; + @Qualifier("modelServicesWebClient") + private final WebClient modelServicesWebClient; + @Qualifier("healthChecksWebClient") + private final WebClient healthChecksWebClient; /** - * Sends POST operation to DMI with json body containing module references. + * Sends a synchronous (blocking) POST operation to the DMI with a JSON body containing module references. * - * @param dmiResourceUrl dmi resource url - * @param requestBodyAsJsonString json data body - * @param operationType the type of operation being executed (for error reporting only) - * @param authorization contents of Authorization header, or null if not present - * @return response entity of type String + * @param requiredDmiService Determines if the required service is for a data or model operation. + * @param dmiUrl The DMI resource URL. + * @param requestBodyAsJsonString JSON data body. + * @param operationType The type of operation being executed (for error reporting only). + * @param authorization Contents of the Authorization header, or null if not present. + * @return ResponseEntity containing the response from the DMI. + * @throws DmiClientRequestException If there is an error during the DMI request. */ - public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl, + public ResponseEntity<Object> synchronousPostOperationWithJsonData(final RequiredDmiService requiredDmiService, + final String dmiUrl, + final String requestBodyAsJsonString, + final OperationType operationType, + final String authorization) { + final Mono<ResponseEntity<Object>> responseEntityMono = + asynchronousPostOperationWithJsonData(requiredDmiService, + dmiUrl, + requestBodyAsJsonString, + operationType, + authorization); + return responseEntityMono.block(); + } + + /** + * Asynchronously performs an HTTP POST operation with the given JSON data. + * + * @param requiredDmiService The service object required for retrieving or configuring the WebClient. + * @param dmiUrl The URL to which the POST request is sent. + * @param requestBodyAsJsonString The JSON string that will be sent as the request body. + * @param operationType An enumeration or object that holds information about the type of operation + * being performed. + * @param authorization The authorization token to be added to the request headers. + * @return A Mono emitting the response entity containing the server's response. + */ + public Mono<ResponseEntity<Object>> asynchronousPostOperationWithJsonData( + final RequiredDmiService requiredDmiService, + final String dmiUrl, final String requestBodyAsJsonString, final OperationType operationType, final String authorization) { - final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders(), - authorization)); - try { - return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class); - } catch (final HttpStatusCodeException httpStatusCodeException) { - final String exceptionMessage = "Unable to " + operationType.toString() + " resource data."; - throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(), - httpStatusCodeException.getStatusCode().value()); - } + final WebClient webClient = getWebClient(requiredDmiService); + return webClient.post() + .uri(toUri(dmiUrl)) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) + .body(BodyInserters.fromValue(requestBodyAsJsonString)) + .retrieve() + .toEntity(Object.class) + .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName())); } /** * Get DMI plugin health status. * - * @param dmiPluginBaseUrl the base URL of the dmi-plugin - * @return plugin health status ("UP" is all OK, "" (not-specified) in case of any exception) + * @param dmiUrl the base URL of the dmi-plugin + * @return plugin health status ("UP" is all OK, "" (not-specified) in case of any exception) */ - public String getDmiHealthStatus(final String dmiPluginBaseUrl) { - final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders(), null)); + public String getDmiHealthStatus(final String dmiUrl) { try { - final JsonNode responseHealthStatus = - restTemplate.getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION, - JsonNode.class, httpHeaders); + final URI dmiHealthCheckUri = toUri(dmiUrl + HEALTH_CHECK_URL_EXTENSION); + final JsonNode responseHealthStatus = healthChecksWebClient.get() + .uri(dmiHealthCheckUri) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) + .retrieve() + .bodyToMono(JsonNode.class).block(); return responseHealthStatus == null ? NOT_SPECIFIED : - responseHealthStatus.get("status").asText(); + responseHealthStatus.path("status").asText(); } catch (final Exception e) { - log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiPluginBaseUrl, e.getMessage()); + log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiUrl, e.getMessage()); return NOT_SPECIFIED; } } - private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders, final String authorization) { + private WebClient getWebClient(final RequiredDmiService requiredDmiService) { + return requiredDmiService.equals(RequiredDmiService.DATA) ? dataServicesWebClient : modelServicesWebClient; + } + + private void configureHttpHeaders(final HttpHeaders httpHeaders, final String authorization) { if (dmiProperties.isDmiBasicAuthEnabled()) { httpHeaders.setBasicAuth(dmiProperties.getAuthUsername(), dmiProperties.getAuthPassword()); } else if (authorization != null && authorization.toLowerCase(Locale.getDefault()).startsWith("bearer ")) { httpHeaders.add(HttpHeaders.AUTHORIZATION, authorization); } - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - return httpHeaders; + } + + private static URI toUri(final String dmiResourceUrl) { + try { + return new URI(dmiResourceUrl); + } catch (final URISyntaxException e) { + throw new InvalidDmiResourceUrlException(dmiResourceUrl, BAD_REQUEST.value()); + } + } + + private DmiClientRequestException handleDmiClientException(final Throwable throwable, final String operationType) { + if (throwable instanceof WebClientResponseException webClientResponseException) { + if (webClientResponseException.getStatusCode().isSameCodeAs(REQUEST_TIMEOUT)) { + throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(), + webClientResponseException.getMessage(), + jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()), + DMI_SERVICE_NOT_RESPONDING); + } + throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(), + webClientResponseException.getMessage(), + jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()), + UNABLE_TO_READ_RESOURCE_DATA); + + } + final String exceptionMessage = "Unable to " + operationType + " resource data."; + if (throwable instanceof WebClientRequestException webClientRequestException) { + throw new DmiClientRequestException(HttpStatus.SERVICE_UNAVAILABLE.value(), + webClientRequestException.getMessage(), + exceptionMessage, DMI_SERVICE_NOT_RESPONDING); + } + if (throwable instanceof HttpServerErrorException httpServerErrorException) { + throw new DmiClientRequestException(httpServerErrorException.getStatusCode().value(), exceptionMessage, + httpServerErrorException.getResponseBodyAsString(), DMI_SERVICE_NOT_RESPONDING); + } + throw new DmiClientRequestException(INTERNAL_SERVER_ERROR.value(), exceptionMessage, throwable.getMessage(), + UNKNOWN_ERROR); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java new file mode 100644 index 000000000..5453efecd --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java @@ -0,0 +1,55 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config; + +import lombok.AccessLevel; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Getter +@Component +public class DmiProperties { + @Value("${ncmp.dmi.auth.username}") + private String authUsername; + @Value("${ncmp.dmi.auth.password}") + private String authPassword; + @Getter(AccessLevel.NONE) + @Value("${ncmp.dmi.api.base-path}") + private String dmiBasePath; + @Value("${ncmp.dmi.auth.enabled}") + private boolean dmiBasicAuthEnabled; + + /** + * Removes both leading and trailing slashes if they are present. + * + * @return dmi base path without any slashes ("/") + */ + public String getDmiBasePath() { + if (dmiBasePath.startsWith("/")) { + dmiBasePath = dmiBasePath.substring(1); + } + if (dmiBasePath.endsWith("/")) { + dmiBasePath = dmiBasePath.substring(0, dmiBasePath.length() - 1); + } + return dmiBasePath; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java new file mode 100644 index 000000000..3a861a68b --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java @@ -0,0 +1,145 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import io.netty.resolver.DefaultAddressResolverGroup; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; + +/** + * Configures and creates WebClient beans for various DMI services including data, model, and health check services. + * The configuration utilizes Netty-based HttpClient with custom connection settings, read and write timeouts, + * and initializes WebClient with these settings to ensure optimal performance and resource management. + */ +@Configuration +@RequiredArgsConstructor +public class DmiWebClientConfiguration { + + private final HttpClientConfiguration httpClientConfiguration; + + private static final Duration DEFAULT_RESPONSE_TIMEOUT = Duration.ofSeconds(30); + + /** + * Configures and creates a WebClient bean for DMI data services. + * + * @return a WebClient instance configured for data services. + */ + @Bean + public WebClient dataServicesWebClient() { + final HttpClientConfiguration.DataServices dataServiceConfig = httpClientConfiguration.getDataServices(); + final ConnectionProvider dataServicesConnectionProvider + = getConnectionProvider(dataServiceConfig.getConnectionProviderName(), + dataServiceConfig.getMaximumConnectionsTotal(), dataServiceConfig.getPendingAcquireMaxCount()); + final HttpClient dataServicesHttpClient = createHttpClient(dataServiceConfig, dataServicesConnectionProvider); + return buildAndGetWebClient(dataServicesHttpClient, dataServiceConfig.getMaximumInMemorySizeInMegabytes()); + } + + /** + * Configures and creates a WebClient bean for DMI model services. + * + * @return a WebClient instance configured for model services. + */ + @Bean + public WebClient modelServicesWebClient() { + final HttpClientConfiguration.ModelServices modelServiceConfig = httpClientConfiguration.getModelServices(); + final ConnectionProvider modelServicesConnectionProvider + = getConnectionProvider(modelServiceConfig.getConnectionProviderName(), + modelServiceConfig.getMaximumConnectionsTotal(), + modelServiceConfig.getPendingAcquireMaxCount()); + final HttpClient modelServicesHttpClient + = createHttpClient(modelServiceConfig, modelServicesConnectionProvider); + return buildAndGetWebClient(modelServicesHttpClient, modelServiceConfig.getMaximumInMemorySizeInMegabytes()); + } + + /** + * Configures and creates a WebClient bean for DMI health check services. + * + * @return a WebClient instance configured for health check services. + */ + @Bean + public WebClient healthChecksWebClient() { + final HttpClientConfiguration.HealthCheckServices healthCheckServiceConfig + = httpClientConfiguration.getHealthCheckServices(); + final ConnectionProvider healthChecksConnectionProvider + = getConnectionProvider(healthCheckServiceConfig.getConnectionProviderName(), + healthCheckServiceConfig.getMaximumConnectionsTotal(), + healthCheckServiceConfig.getPendingAcquireMaxCount()); + final HttpClient healthChecksHttpClient + = createHttpClient(healthCheckServiceConfig, healthChecksConnectionProvider); + return buildAndGetWebClient(healthChecksHttpClient, + healthCheckServiceConfig.getMaximumInMemorySizeInMegabytes()); + } + + /** + * Provides a WebClient.Builder bean for creating WebClient instances. + * + * @return a WebClient.Builder instance. + */ + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + private static HttpClient createHttpClient(final HttpClientConfiguration.ServiceConfig serviceConfig, + final ConnectionProvider connectionProvider) { + return HttpClient.create(connectionProvider) + .responseTimeout(DEFAULT_RESPONSE_TIMEOUT) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serviceConfig.getConnectionTimeoutInSeconds() * 1000) + .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler( + serviceConfig.getReadTimeoutInSeconds(), TimeUnit.SECONDS)).addHandlerLast( + new WriteTimeoutHandler(serviceConfig.getWriteTimeoutInSeconds(), TimeUnit.SECONDS))) + .resolver(DefaultAddressResolverGroup.INSTANCE) + .compress(true); + } + + @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") + private static ConnectionProvider getConnectionProvider(final String connectionProviderName, + final int maximumConnectionsTotal, + final int pendingAcquireMaxCount) { + return ConnectionProvider.builder(connectionProviderName) + .maxConnections(maximumConnectionsTotal) + .pendingAcquireMaxCount(pendingAcquireMaxCount) + .build(); + } + + private WebClient buildAndGetWebClient(final HttpClient httpClient, + final int maximumInMemorySizeInMegabytes) { + return webClientBuilder() + .defaultHeaders(header -> header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)) + .defaultHeaders(header -> header.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .codecs(configurer -> configurer.defaultCodecs() + .maxInMemorySize(maximumInMemorySizeInMegabytes * 1024 * 1024)).build(); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java index d547e31c6..0acbabbba 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. + * Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,38 +20,53 @@ package org.onap.cps.ncmp.api.impl.config; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.convert.DurationUnit; +import org.springframework.context.annotation.Configuration; @Getter @Setter -@ConfigurationProperties(prefix = "ncmp.dmi.httpclient", ignoreUnknownFields = true) +@Configuration +@ConfigurationProperties(prefix = "ncmp.dmi.httpclient") public class HttpClientConfiguration { - /** - * The maximum time to establish a connection. - */ - @DurationUnit(ChronoUnit.SECONDS) - private Duration connectionTimeoutInSeconds = Duration.ofSeconds(180); + private final DataServices dataServices = new DataServices(); + private final ModelServices modelServices = new ModelServices(); + private final HealthCheckServices healthCheckServices = new HealthCheckServices(); - /** - * The maximum number of open connections per route. - */ - private int maximumConnectionsPerRoute = 50; + @Getter + @Setter + public static class DataServices extends ServiceConfig { + private String connectionProviderName = "dataConnectionPool"; + } - /** - * The maximum total number of open connections. - */ - private int maximumConnectionsTotal = maximumConnectionsPerRoute * 2; + @Getter + @Setter + public static class ModelServices extends ServiceConfig { + private String connectionProviderName = "modelConnectionPool"; + } + + @Getter + @Setter + public static class HealthCheckServices extends ServiceConfig { + private String connectionProviderName = "healthConnectionPool"; + private int maximumConnectionsTotal = 10; + private int pendingAcquireMaxCount = 5; + } /** - * The duration after which idle connections are evicted. + * Base configuration properties for all services. */ - @DurationUnit(ChronoUnit.SECONDS) - private Duration idleConnectionEvictionThresholdInSeconds = Duration.ofSeconds(5); - + @Getter + @Setter + public static class ServiceConfig { + private String connectionProviderName = "cpsConnectionPool"; + private int maximumConnectionsTotal = 100; + private int pendingAcquireMaxCount = 50; + private Integer connectionTimeoutInSeconds = 30; + private long readTimeoutInSeconds = 30; + private long writeTimeoutInSeconds = 30; + private int maximumInMemorySizeInMegabytes = 1; + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java deleted file mode 100644 index c6ff116a7..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.config; - -import java.util.Arrays; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.apache.hc.client5.http.config.ConnectionConfig; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; -import org.apache.hc.core5.util.TimeValue; -import org.apache.hc.core5.util.Timeout; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -@Configuration -@EnableConfigurationProperties(HttpClientConfiguration.class) -@RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public class NcmpConfiguration { - - @Getter - @Component - public static class DmiProperties { - @Value("${ncmp.dmi.auth.username}") - private String authUsername; - @Value("${ncmp.dmi.auth.password}") - private String authPassword; - @Value("${ncmp.dmi.api.base-path}") - private String dmiBasePath; - @Value("${ncmp.dmi.auth.enabled}") - private boolean dmiBasicAuthEnabled; - } - - /** - * Rest template bean. - * - * @param restTemplateBuilder the rest template builder - * @param httpClientConfiguration the http client configuration - * @return rest template instance - */ - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) - public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder, - final HttpClientConfiguration httpClientConfiguration) { - - final ConnectionConfig connectionConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT) - .setConnectTimeout(Timeout.of(httpClientConfiguration.getConnectionTimeoutInSeconds())) - .build(); - - final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() - .setDefaultConnectionConfig(connectionConfig) - .setMaxConnTotal(httpClientConfiguration.getMaximumConnectionsTotal()) - .setMaxConnPerRoute(httpClientConfiguration.getMaximumConnectionsPerRoute()) - .build(); - - final CloseableHttpClient httpClient = HttpClients.custom() - .setConnectionManager(connectionManager) - .evictExpiredConnections() - .evictIdleConnections( - TimeValue.of(httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds())) - .build(); - - final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - - final RestTemplate restTemplate = restTemplateBuilder - .requestFactory(() -> requestFactory) - .setConnectTimeout(httpClientConfiguration.getConnectionTimeoutInSeconds()) - .build(); - - setRestTemplateMessageConverters(restTemplate); - return restTemplate; - } - - private static void setRestTemplateMessageConverters(final RestTemplate restTemplate) { - final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = - new MappingJackson2HttpMessageConverter(); - mappingJackson2HttpMessageConverter.setSupportedMediaTypes( - Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); - restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter); - } - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfig.java new file mode 100644 index 000000000..bcbacbd42 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfig.java @@ -0,0 +1,111 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config; + +import io.micrometer.observation.ObservationPredicate; +import io.micrometer.observation.ObservationRegistry; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.time.Duration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.observation.ServerRequestObservationContext; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + +@Configuration +public class OpenTelemetryConfig { + + public static final int JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECOND = 30; + + @Value("${spring.application.name:cps-application}") + private String serviceId; + + @Value("${cps.tracing.exporter.endpoint:http://onap-otel-collector:4317}") + private String tracingExporterEndpointUrl; + + @Value("${cps.tracing.sampler.jaeger_remote.endpoint:http://onap-otel-collector:14250}") + private String jaegerRemoteSamplerUrl; + + /** + * OTLP Exporter with Grpc exporter protocol. + */ + @Bean + @ConditionalOnExpression( + "${cps.tracing.enabled} && 'grpc'.equals('${cps.tracing.exporter.protocol}')") + public OtlpGrpcSpanExporter createOtlpExporterGrpc() { + return OtlpGrpcSpanExporter.builder().setEndpoint(tracingExporterEndpointUrl).build(); + } + + /** + * OTLP Exporter with HTTP exporter protocol. + */ + @Bean + @ConditionalOnExpression( + "${cps.tracing.enabled} && 'http'.equals('${cps.tracing.exporter.protocol}')") + public OtlpHttpSpanExporter createOtlpExporterHttp() { + return OtlpHttpSpanExporter.builder().setEndpoint(tracingExporterEndpointUrl).build(); + } + + /** + * Jaeger Remote Sampler. + */ + @Bean + @ConditionalOnProperty("cps.tracing.enabled") + public JaegerRemoteSampler createJaegerRemoteSampler() { + return JaegerRemoteSampler.builder() + .setEndpoint(jaegerRemoteSamplerUrl) + .setPollingInterval(Duration.ofSeconds(JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECOND)) + .setInitialSampler(Sampler.alwaysOff()) + .setServiceName(serviceId) + .build(); + } + + /** + * Excluding /actuator/** endpoints. + */ + @Bean + @ConditionalOnProperty("cps.tracing.enabled") + ObservationRegistryCustomizer<ObservationRegistry> skipActuatorEndpointsFromObservation() { + final PathMatcher pathMatcher = new AntPathMatcher("/"); + return registry -> + registry.observationConfig().observationPredicate(observationPredicate(pathMatcher)); + } + + /** + * Excluding /actuator/** endpoints. + */ + static ObservationPredicate observationPredicate(final PathMatcher pathMatcher) { + return (name, context) -> { + if (context instanceof ServerRequestObservationContext observationContext) { + return !pathMatcher.match("/actuator/**", observationContext.getCarrier().getRequestURI()); + } else { + return true; + } + }; + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfig.java index 167df5a98..cf6f1c5b1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfig.java @@ -21,10 +21,14 @@ package org.onap.cps.ncmp.api.impl.config.kafka; import io.cloudevents.CloudEvent; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; import java.time.Duration; import java.util.Map; import lombok.RequiredArgsConstructor; +import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; @@ -52,6 +56,9 @@ public class KafkaConfig<T> { private final KafkaProperties kafkaProperties; + @Value("${cps.tracing.enabled:false}") + private boolean tracingEnabled; + private static final SslBundles NO_SSL = null; /** @@ -64,6 +71,10 @@ public class KafkaConfig<T> { public ProducerFactory<String, T> legacyEventProducerFactory() { final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties(NO_SSL); producerConfigProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + if (tracingEnabled) { + producerConfigProperties.put( + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); + } return new DefaultKafkaProducerFactory<>(producerConfigProperties); } @@ -77,6 +88,10 @@ public class KafkaConfig<T> { public ConsumerFactory<String, T> legacyEventConsumerFactory() { final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties(NO_SSL); consumerConfigProperties.put("spring.deserializer.value.delegate.class", JsonDeserializer.class); + if (tracingEnabled) { + consumerConfigProperties.put( + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); + } return new DefaultKafkaConsumerFactory<>(consumerConfigProperties); } @@ -90,6 +105,9 @@ public class KafkaConfig<T> { public KafkaTemplate<String, T> legacyEventKafkaTemplate() { final KafkaTemplate<String, T> kafkaTemplate = new KafkaTemplate<>(legacyEventProducerFactory()); kafkaTemplate.setConsumerFactory(legacyEventConsumerFactory()); + if (tracingEnabled) { + kafkaTemplate.setObservationEnabled(true); + } return kafkaTemplate; } @@ -104,6 +122,9 @@ public class KafkaConfig<T> { new ConcurrentKafkaListenerContainerFactory<>(); containerFactory.setConsumerFactory(legacyEventConsumerFactory()); containerFactory.getContainerProperties().setAuthExceptionRetryInterval(Duration.ofSeconds(10)); + if (tracingEnabled) { + containerFactory.getContainerProperties().setObservationEnabled(true); + } return containerFactory; } @@ -116,6 +137,10 @@ public class KafkaConfig<T> { @Bean public ProducerFactory<String, CloudEvent> cloudEventProducerFactory() { final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties(NO_SSL); + if (tracingEnabled) { + producerConfigProperties.put( + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); + } return new DefaultKafkaProducerFactory<>(producerConfigProperties); } @@ -128,6 +153,10 @@ public class KafkaConfig<T> { @Bean public ConsumerFactory<String, CloudEvent> cloudEventConsumerFactory() { final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties(NO_SSL); + if (tracingEnabled) { + consumerConfigProperties.put( + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); + } return new DefaultKafkaConsumerFactory<>(consumerConfigProperties); } @@ -142,6 +171,9 @@ public class KafkaConfig<T> { final KafkaTemplate<String, CloudEvent> kafkaTemplate = new KafkaTemplate<>(cloudEventProducerFactory()); kafkaTemplate.setConsumerFactory(cloudEventConsumerFactory()); + if (tracingEnabled) { + kafkaTemplate.setObservationEnabled(true); + } return kafkaTemplate; } @@ -157,6 +189,9 @@ public class KafkaConfig<T> { new ConcurrentKafkaListenerContainerFactory<>(); containerFactory.setConsumerFactory(cloudEventConsumerFactory()); containerFactory.getContainerProperties().setAuthExceptionRetryInterval(Duration.ofSeconds(10)); + if (tracingEnabled) { + containerFactory.getContainerProperties().setObservationEnabled(true); + } return containerFactory; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java index 8a4beb956..4e2062fed 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java @@ -64,12 +64,19 @@ public class CmNotificationSubscriptionDelta { } } - final DmiCmNotificationSubscriptionPredicate predicateDelta = - new DmiCmNotificationSubscriptionPredicate(targetCmHandleIds, datastoreType, xpaths); + populateValidDmiCmNotificationSubscriptionPredicateDelta(targetCmHandleIds, xpaths, datastoreType, delta); + } + return delta; + } + private void populateValidDmiCmNotificationSubscriptionPredicateDelta(final Set<String> targetCmHandleIds, + final Set<String> xpaths, final DatastoreType datastoreType, + final List<DmiCmNotificationSubscriptionPredicate> delta) { + if (!(targetCmHandleIds.isEmpty() || xpaths.isEmpty())) { + final DmiCmNotificationSubscriptionPredicate predicateDelta = + new DmiCmNotificationSubscriptionPredicate(targetCmHandleIds, datastoreType, xpaths); delta.add(predicateDelta); } - return delta; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java index b5370bf1e..21c26228b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java @@ -35,10 +35,10 @@ import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubs import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate; import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.operations.DatastoreType; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.springframework.stereotype.Component; @Component @@ -123,12 +123,12 @@ public class DmiCmNotificationSubscriptionCacheHandler { * */ public void updateDmiCmNotificationSubscriptionStatusPerDmi(final String subscriptionId, - final String dmiServiceName, final CmNotificationSubscriptionStatus status) { + final String dmiServiceName, + final CmNotificationSubscriptionStatus status) { final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi = cmNotificationSubscriptionCache.get(subscriptionId); dmiCmNotificationSubscriptionDetailsPerDmi.get(dmiServiceName).setCmNotificationSubscriptionStatus(status); cmNotificationSubscriptionCache.put(subscriptionId, dmiCmNotificationSubscriptionDetailsPerDmi); - } /** @@ -157,6 +157,32 @@ public class DmiCmNotificationSubscriptionCacheHandler { } } + /** + * Remove subscription from database per DMI service name. + * + * @param subscriptionId String of subscription id + * @param dmiServiceName String of dmiServiceName + * + */ + public void removeFromDatabasePerDmi(final String subscriptionId, final String dmiServiceName) { + final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicateList = + cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName) + .getDmiCmNotificationSubscriptionPredicates(); + for (final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate: + dmiCmNotificationSubscriptionPredicateList) { + final DatastoreType datastoreType = dmiCmNotificationSubscriptionPredicate.getDatastoreType(); + final Set<String> cmHandles = dmiCmNotificationSubscriptionPredicate.getTargetCmHandleIds(); + final Set<String> xpaths = dmiCmNotificationSubscriptionPredicate.getXpaths(); + + for (final String cmHandle: cmHandles) { + for (final String xpath: xpaths) { + cmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(datastoreType, + cmHandle, xpath, subscriptionId); + } + } + } + } + private void updateDmiCmNotificationSubscriptionDetailsPerDmi( final String dmiServiceName, final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate, @@ -190,4 +216,4 @@ public class DmiCmNotificationSubscriptionCacheHandler { return dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("ACCEPTED") || dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("REJECTED"); } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java index fb89aae3f..051949c5e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java @@ -20,6 +20,8 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer; +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_DATA_SUBSCRIPTION_ACCEPTED; +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_DATA_SUBSCRIPTION_REJECTED; import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; import io.cloudevents.CloudEvent; @@ -27,12 +29,14 @@ import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.Data; import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @@ -46,12 +50,14 @@ public class CmNotificationSubscriptionDmiOutEventConsumer { private final CmNotificationSubscriptionEventsHandler cmNotificationSubscriptionEventsHandler; private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; + private static final String CM_DATA_SUBSCRIPTION_CORRELATION_ID_SEPARATOR = "#"; + /** * Consume the Cm Notification Subscription event from the dmi-plugin. * * @param cmNotificationSubscriptionDmiOutEventConsumerRecord the event to be consumed */ - @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}", + @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-dmi-out}", containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory") public void consumeCmNotificationSubscriptionDmiOutEvent( final ConsumerRecord<String, CloudEvent> cmNotificationSubscriptionDmiOutEventConsumerRecord) { @@ -59,26 +65,33 @@ public class CmNotificationSubscriptionDmiOutEventConsumer { final CmNotificationSubscriptionDmiOutEvent cmNotificationSubscriptionDmiOutEvent = toTargetEvent(cloudEvent, CmNotificationSubscriptionDmiOutEvent.class); final String correlationId = String.valueOf(cloudEvent.getExtension("correlationid")); - if ("subscriptionCreateResponse".equals(cloudEvent.getType()) && cmNotificationSubscriptionDmiOutEvent != null - && correlationId != null) { - handleCmSubscriptionCreate(correlationId, cmNotificationSubscriptionDmiOutEvent); + if (cmNotificationSubscriptionDmiOutEvent != null && correlationId != null) { + final String eventType = cloudEvent.getType(); + handleCmSubscriptionDmiOutEvent(correlationId, eventType, cmNotificationSubscriptionDmiOutEvent); } } - private void handleCmSubscriptionCreate(final String correlationId, - final CmNotificationSubscriptionDmiOutEvent cmNotificationSubscriptionDmiOutEvent) { - final String subscriptionId = correlationId.split("#")[0]; - final String dmiPluginName = correlationId.split("#")[1]; + private void handleCmSubscriptionDmiOutEvent(final String correlationId, + final String eventType, + final CmNotificationSubscriptionDmiOutEvent + cmNotificationSubscriptionDmiOutEvent) { + final String subscriptionId = correlationId.split(CM_DATA_SUBSCRIPTION_CORRELATION_ID_SEPARATOR)[0]; + final String dmiPluginName = correlationId.split(CM_DATA_SUBSCRIPTION_CORRELATION_ID_SEPARATOR)[1]; - if ("ACCEPTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) { + if (checkStatusCodeAndMessage(CM_DATA_SUBSCRIPTION_ACCEPTED, cmNotificationSubscriptionDmiOutEvent.getData())) { handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.ACCEPTED); - dmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName); - handleEventsStatusPerDmi(subscriptionId); + if (eventType.equals("subscriptionCreateResponse")) { + dmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName); + } + if (eventType.equals("subscriptionDeleteResponse")) { + dmiCmNotificationSubscriptionCacheHandler.removeFromDatabasePerDmi(subscriptionId, dmiPluginName); + } + handleEventsStatusPerDmi(subscriptionId, eventType); } - if ("REJECTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) { + if (checkStatusCodeAndMessage(CM_DATA_SUBSCRIPTION_REJECTED, cmNotificationSubscriptionDmiOutEvent.getData())) { handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.REJECTED); - handleEventsStatusPerDmi(subscriptionId); + handleEventsStatusPerDmi(subscriptionId, eventType); } log.info("Cm Subscription with id : {} handled by the dmi-plugin : {} has the status : {}", subscriptionId, @@ -86,18 +99,25 @@ public class CmNotificationSubscriptionDmiOutEventConsumer { } private void handleCacheStatusPerDmi(final String subscriptionId, final String dmiPluginName, - final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) { + final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) { dmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi(subscriptionId, dmiPluginName, cmNotificationSubscriptionStatus); } - private void handleEventsStatusPerDmi(final String subscriptionId) { + private void handleEventsStatusPerDmi(final String subscriptionId, final String eventType) { final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi = dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent = cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, dmiCmNotificationSubscriptionDetailsPerDmi); cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, - "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); + eventType, cmNotificationSubscriptionNcmpOutEvent, false); + } + + private boolean checkStatusCodeAndMessage(final NcmpResponseStatus ncmpResponseStatus, + final Data cmNotificationSubscriptionDmiOutData) { + return ncmpResponseStatus.getCode().equals(cmNotificationSubscriptionDmiOutData.getStatusCode()) + && ncmpResponseStatus.getMessage() + .equals(cmNotificationSubscriptionDmiOutData.getStatusMessage()); } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java index 70135b307..fb3388c11 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java @@ -23,12 +23,13 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer; import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; import io.cloudevents.CloudEvent; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionHandlerService; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent; -import org.springframework.beans.factory.annotation.Value; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @@ -39,15 +40,12 @@ public class CmNotificationSubscriptionNcmpInEventConsumer { private final CmNotificationSubscriptionHandlerService cmNotificationSubscriptionHandlerService; - @Value("${notification.enabled:true}") - private boolean notificationFeatureEnabled; - /** * Consume the specified event. * * @param subscriptionEventConsumerRecord the event to be consumed */ - @KafkaListener(topics = "${app.ncmp.avc.subscription-topic}", + @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-ncmp-in}", containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory") public void consumeSubscriptionEvent(final ConsumerRecord<String, CloudEvent> subscriptionEventConsumerRecord) { final CloudEvent cloudEvent = subscriptionEventConsumerRecord.value(); @@ -57,10 +55,16 @@ public class CmNotificationSubscriptionNcmpInEventConsumer { cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId()); final String subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId(); + final List<Predicate> predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates(); if ("subscriptionCreateRequest".equals(cloudEvent.getType())) { - log.info("Subscription for source {} with subscription id {} ...", cloudEvent.getSource(), subscriptionId); - cmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest( - cmNotificationSubscriptionNcmpInEvent); + log.info("Subscription create request for source {} with subscription id {} ...", + cloudEvent.getSource(), subscriptionId); + cmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(subscriptionId, predicates); + } + if ("subscriptionDeleteRequest".equals(cloudEvent.getType())) { + log.info("Subscription delete request for source {} with subscription id {} ...", + cloudEvent.getSource(), subscriptionId); + cmNotificationSubscriptionHandlerService.processSubscriptionDeleteRequest(subscriptionId, predicates); } } }
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapper.java index 489401f26..7263891a2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapper.java @@ -28,12 +28,12 @@ import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmHandle; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; -import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Data; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Predicate; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.ScopeFilter; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.springframework.stereotype.Component; @Component @@ -54,7 +54,7 @@ public class CmNotificationSubscriptionDmiInEventMapper { new CmNotificationSubscriptionDmiInEvent(); final Data cmSubscriptionData = new Data(); cmSubscriptionData.setPredicates(mapToDmiInEventPredicates(dmiCmNotificationSubscriptionPredicates)); - cmSubscriptionData.setCmhandles(mapToCmSubscriptionCmhandleWithPrivateProperties( + cmSubscriptionData.setCmHandles(mapToCmSubscriptionCmhandleWithPrivateProperties( extractUniqueCmHandleIds(dmiCmNotificationSubscriptionPredicates))); cmNotificationSubscriptionDmiInEvent.setData(cmSubscriptionData); return cmNotificationSubscriptionDmiInEvent; @@ -81,12 +81,12 @@ public class CmNotificationSubscriptionDmiInEventMapper { } - private List<Cmhandle> mapToCmSubscriptionCmhandleWithPrivateProperties(final Set<String> cmHandleIds) { + private List<CmHandle> mapToCmSubscriptionCmhandleWithPrivateProperties(final Set<String> cmHandleIds) { - final List<Cmhandle> cmSubscriptionCmHandles = new ArrayList<>(); + final List<CmHandle> cmSubscriptionCmHandles = new ArrayList<>(); inventoryPersistence.getYangModelCmHandles(cmHandleIds).forEach(yangModelCmHandle -> { - final Cmhandle cmhandle = new Cmhandle(); + final CmHandle cmhandle = new CmHandle(); final Map<String, String> cmhandleDmiProperties = new LinkedHashMap<>(); yangModelCmHandle.getDmiProperties() .forEach(dmiProperty -> cmhandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue())); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java index 9fbe26848..3273c556c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java @@ -25,7 +25,6 @@ import io.cloudevents.core.builder.CloudEventBuilder; import java.net.URI; import java.util.UUID; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.events.EventsPublisher; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; import org.onap.cps.utils.JsonObjectMapper; @@ -34,7 +33,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @Component -@Slf4j @RequiredArgsConstructor @ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true) public class CmNotificationSubscriptionDmiInEventProducer { @@ -42,7 +40,7 @@ public class CmNotificationSubscriptionDmiInEventProducer { private final EventsPublisher<CloudEvent> eventsPublisher; private final JsonObjectMapper jsonObjectMapper; - @Value("${app.ncmp.avc.subscription-forward-topic-prefix}") + @Value("${app.ncmp.avc.cm-subscription-dmi-in}") private String cmNotificationSubscriptionDmiInEventTopic; /** @@ -65,9 +63,10 @@ public class CmNotificationSubscriptionDmiInEventProducer { final String dmiPluginName, final String eventType, final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent) { return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType) - .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0")) - .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName)) - .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionDmiInEvent)).build(); + .withSource(URI.create("NCMP")) + .withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0")) + .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName)) + .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionDmiInEvent)).build(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java index ac5de07f0..ed7ed2a0b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; @ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true) public class CmNotificationSubscriptionNcmpOutEventProducer { - @Value("${app.ncmp.avc.subscription-outcome-topic}") + @Value("${app.ncmp.avc.cm-subscription-ncmp-out}") private String cmNotificationSubscriptionNcmpOutEventTopic; @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms}") diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerService.java index 536693ee4..1c52ffa79 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerService.java @@ -20,16 +20,25 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service; -import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent; +import java.util.List; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate; public interface CmNotificationSubscriptionHandlerService { /** - * Process cm notification subscription request. + * Process cm notification subscription create request. * - * @param cmNotificationSubscriptionNcmpInEvent CM Notification Subscription event + * @param subscriptionId subscription id + * @param predicates subscription predicates */ - void processSubscriptionCreateRequest( - final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent); + void processSubscriptionCreateRequest(final String subscriptionId, final List<Predicate> predicates); -} + /** + * Process cm notification subscription delete request. + * + * @param subscriptionId subscription id + * @param predicates subscription predicates + */ + void processSubscriptionDeleteRequest(final String subscriptionId, final List<Predicate> predicates); + +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java index 7872ba0a3..08e3c9552 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java @@ -30,9 +30,9 @@ import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscripti import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate; -import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; @@ -49,28 +49,32 @@ public class CmNotificationSubscriptionHandlerServiceImpl implements CmNotificat private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; @Override - public void processSubscriptionCreateRequest( - final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent) { - final String subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId(); - final List<Predicate> predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates(); - + public void processSubscriptionCreateRequest(final String subscriptionId, final List<Predicate> predicates) { if (cmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId(subscriptionId)) { dmiCmNotificationSubscriptionCacheHandler.add(subscriptionId, predicates); - sendSubscriptionCreateRequestToDmi(subscriptionId); - scheduleCmNotificationSubscriptionNcmpOutEventResponse(subscriptionId); + handleCmNotificationSubscriptionDelta(subscriptionId); + scheduleCmNotificationSubscriptionNcmpOutEventResponse(subscriptionId, + "subscriptionCreateResponse"); } else { - rejectAndPublishCmNotificationSubscriptionCreateRequest(subscriptionId, - predicates); + rejectAndPublishCmNotificationSubscriptionCreateRequest(subscriptionId, predicates); } } - private void scheduleCmNotificationSubscriptionNcmpOutEventResponse(final String subscriptionId) { + @Override + public void processSubscriptionDeleteRequest(final String subscriptionId, final List<Predicate> predicates) { + dmiCmNotificationSubscriptionCacheHandler.add(subscriptionId, predicates); + sendSubscriptionDeleteRequestToDmi(subscriptionId); + scheduleCmNotificationSubscriptionNcmpOutEventResponse(subscriptionId, "subscriptionDeleteResponse"); + } + + private void scheduleCmNotificationSubscriptionNcmpOutEventResponse(final String subscriptionId, + final String eventType) { cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, - "subscriptionCreateResponse", null, true); + eventType, null, true); } private void rejectAndPublishCmNotificationSubscriptionCreateRequest(final String subscriptionId, - final List<Predicate> predicates) { + final List<Predicate> predicates) { final Set<String> subscriptionTargetFilters = predicates.stream().flatMap(predicate -> predicate.getTargetFilter().stream()) .collect(Collectors.toSet()); @@ -81,18 +85,50 @@ public class CmNotificationSubscriptionHandlerServiceImpl implements CmNotificat "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); } - private void sendSubscriptionCreateRequestToDmi(final String subscriptionId) { + private void handleCmNotificationSubscriptionDelta(final String subscriptionId) { final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap = dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); dmiCmNotificationSubscriptionDetailsMap.forEach((dmiPluginName, dmiCmNotificationSubscriptionDetails) -> { final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates = cmNotificationSubscriptionDelta.getDelta( dmiCmNotificationSubscriptionDetails.getDmiCmNotificationSubscriptionPredicates()); + + if (dmiCmNotificationSubscriptionPredicates.isEmpty()) { + acceptAndPublishCmNotificationSubscriptionNcmpOutEventPerDmi(subscriptionId, dmiPluginName); + } else { + publishCmNotificationSubscriptionDmiInEventPerDmi(subscriptionId, dmiPluginName, + dmiCmNotificationSubscriptionPredicates); + } + }); + } + + private void publishCmNotificationSubscriptionDmiInEventPerDmi(final String subscriptionId, + final String dmiPluginName, + final List<DmiCmNotificationSubscriptionPredicate> + dmiCmNotificationSubscriptionPredicates) { + final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent = + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent( + dmiCmNotificationSubscriptionPredicates); + cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(subscriptionId, + dmiPluginName, "subscriptionCreateRequest", cmNotificationSubscriptionDmiInEvent); + } + + private void acceptAndPublishCmNotificationSubscriptionNcmpOutEventPerDmi(final String subscriptionId, + final String dmiPluginName) { + dmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi(subscriptionId, + dmiPluginName, CmNotificationSubscriptionStatus.ACCEPTED); + dmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName); + } + + private void sendSubscriptionDeleteRequestToDmi(final String subscriptionId) { + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap = + dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); + dmiCmNotificationSubscriptionDetailsMap.forEach((dmiPluginName, dmiCmNotificationSubscriptionDetails) -> { final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent = cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent( - dmiCmNotificationSubscriptionPredicates); + dmiCmNotificationSubscriptionDetails.getDmiCmNotificationSubscriptionPredicates()); cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(subscriptionId, - dmiPluginName, "subscriptionCreateRequest", cmNotificationSubscriptionDmiInEvent); + dmiPluginName, "subscriptionDeleteRequest", cmNotificationSubscriptionDmiInEvent); }); } }
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java index 0adf225fe..a9b1e26f5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service; +import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import java.io.Serializable; @@ -45,10 +47,14 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions"; private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE = """ - /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters + /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s'] """.trim(); + private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE = + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filters"; + private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH = - CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']"; + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']"; + private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """ //filter/subscriptionIds[text()='%s'] @@ -106,12 +112,11 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath); if (subscriptionIds.remove(subscriptionId)) { - if (isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath)) { - saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds); - log.info("There are subscribers left for the following cps path {} :", - CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted( - datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath))); - } else { + saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds); + log.info("There are subscribers left for the following cps path {} :", + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted( + datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath))); + if (subscriptionIds.isEmpty()) { log.info("No subscribers left for the following cps path {} :", CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted( datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath))); @@ -126,11 +131,25 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted( datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)), OffsetDateTime.now()); + final Collection<DataNode> existingFiltersForCmHandle = + cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted( + datastoreType.getDatastoreName(), cmHandleId), + DIRECT_CHILDREN_ONLY).iterator().next().getChildDataNodes(); + if (existingFiltersForCmHandle.isEmpty()) { + removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId); + } + } + + private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) { + cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted( + datastoreName, cmHandleId), OffsetDateTime.now()); } private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) { return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, - CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted( + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted( datastoreType.getDatastoreName(), cmHandleId), OMIT_DESCENDANTS).isEmpty(); } @@ -150,7 +169,7 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif OffsetDateTime.now(), ContentType.JSON); } else { cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, - CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted( + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted( datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now()); } @@ -161,8 +180,9 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif final Collection<String> subscriptionIds) { final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds); cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, - CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted( - datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now()); + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted( + datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, + OffsetDateTime.now(), ContentType.JSON); } private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandler.java index 8274772d2..3a4995786 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandler.java @@ -22,8 +22,8 @@ package org.onap.cps.ncmp.api.impl.events.lcm; import java.util.Collection; import java.util.Map; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; /** * The implementation of it should handle the persisting of composite state and delegate the request to publish the diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java index 3742719e2..b7e6a7457 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java @@ -22,11 +22,11 @@ package org.onap.cps.ncmp.api.impl.events.lcm; import java.util.Collection; import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImpl.java index 0ed95adff..97e519351 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImpl.java @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.events.lcm; -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED; -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED; -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED; -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY; import io.micrometer.core.annotation.Timed; import java.util.ArrayList; @@ -37,13 +37,13 @@ import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateUtils; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.CompositeStateUtils; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.stereotype.Service; @Slf4j diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreator.java index fa27be158..4231c9967 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreator.java @@ -27,7 +27,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.Event; import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader; @@ -128,4 +128,4 @@ public class LcmEventsCreator { private Values newValues; } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorHelper.java index 19d9ba5c0..7c7a9722c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorHelper.java @@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.events.lcm; import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.CREATE; import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.DELETE; import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.UPDATE; -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; @@ -33,7 +33,7 @@ import java.util.Map; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.Values; /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java new file mode 100644 index 000000000..ab0fa6893 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java @@ -0,0 +1,54 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.exception; + +import lombok.Getter; +import org.onap.cps.ncmp.api.NcmpResponseStatus; + +/** + * Http Client Request exception from dmi service. + */ +@Getter +public class DmiClientRequestException extends NcmpException { + + private static final long serialVersionUID = 6659897770659834797L; + final NcmpResponseStatus ncmpResponseStatus; + final String message; + final String responseBodyAsString; + final int httpStatusCode; + + /** + * Constructor to form exception for dmi service response. + * + * @param httpStatusCode http response code from the client + * @param message response message from the client + * @param responseBodyAsString response body from the client + * @param ncmpResponseStatus ncmp status message and code + */ + public DmiClientRequestException(final int httpStatusCode, final String message, final String responseBodyAsString, + final NcmpResponseStatus ncmpResponseStatus) { + super(message, responseBodyAsString); + this.httpStatusCode = httpStatusCode; + this.message = message; + this.responseBodyAsString = responseBodyAsString; + this.ncmpResponseStatus = ncmpResponseStatus; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDmiResourceUrlException.java index 9d307e5d2..270988b63 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDmiResourceUrlException.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,24 +22,16 @@ package org.onap.cps.ncmp.api.impl.exception; import lombok.Getter; -/** - * Http Client Request exception for passthrough scenarios. - */ @Getter -public class HttpClientRequestException extends NcmpException { +public class InvalidDmiResourceUrlException extends RuntimeException { + + private static final long serialVersionUID = 2928476384584894968L; - private static final long serialVersionUID = 6659897770659834797L; + private static final String INVALID_DMI_URL = "Invalid dmi resource url"; final Integer httpStatus; - /** - * Constructor to form exception for passthrough scenarios. - * - * @param message message details from NCMP - * @param details response body from the client available as details - * @param httpStatus http status code from the client - */ - public HttpClientRequestException(final String message, final String details, final Integer httpStatus) { - super(message, details); + public InvalidDmiResourceUrlException(final String details, final Integer httpStatus) { + super(String.format(INVALID_DMI_URL + ": %s", details)); this.httpStatus = httpStatus; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java index 2e66ac0bf..7baac34b1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java @@ -40,7 +40,7 @@ public class DmiDataOperation { private String options; private String resourceIdentifier; - private final List<CmHandle> cmHandles = new ArrayList<>(); + private final List<DmiOperationCmHandle> cmHandles = new ArrayList<>(); /** * Create and initialise a (outgoing) DMI data operation. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index a9ec1241b..32fc8a5db 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -21,10 +21,10 @@ package org.onap.cps.ncmp.api.impl.operations; -import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING; -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA; +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL; import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ; +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA; import io.micrometer.core.annotation.Timed; import java.util.Collection; @@ -32,91 +32,86 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; -import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.impl.config.DmiProperties; +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.utils.data.operation.ResourceDataOperationRequestUtils; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.CmResourceAddress; import org.onap.cps.ncmp.api.models.DataOperationRequest; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; /** * Operations class for DMI data. */ -@Component -@Slf4j -public class DmiDataOperations extends DmiOperations { +@RequiredArgsConstructor +@Service +public class DmiDataOperations { - public DmiDataOperations(final InventoryPersistence inventoryPersistence, - final JsonObjectMapper jsonObjectMapper, - final NcmpConfiguration.DmiProperties dmiProperties, - final DmiRestClient dmiRestClient, - final DmiServiceUrlBuilder dmiServiceUrlBuilder) { - super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); - } + private final InventoryPersistence inventoryPersistence; + private final JsonObjectMapper jsonObjectMapper; + private final DmiProperties dmiProperties; + private final DmiRestClient dmiRestClient; /** - * This method fetches the resource data from operational data store for given cm handle - * identifier on given resource using dmi client. + * This method fetches the resource data from the operational data store for a given CM handle + * identifier on the specified resource using the DMI client. * - * @param cmResourceAddress target datastore, cm handle and resource identifier - * @param optionsParamInQuery options query - * @param topicParamInQuery topic name for (triggering) async responses - * @param requestId requestId for async responses - * @param authorization contents of Authorization header, or null if not present - * @return {@code ResponseEntity} response entity + * @param cmResourceAddress Target datastore, CM handle, and resource identifier. + * @param options Options query string. + * @param topic Topic name for triggering asynchronous responses. + * @param requestId Request ID for asynchronous responses. + * @param authorization Contents of the Authorization header, or null if not present. + * @return {@code Mono<ResponseEntity<Object>>} A reactive type representing the response entity. */ @Timed(value = "cps.ncmp.dmi.get", description = "Time taken to fetch the resource data from operational data store for given cm handle " + "identifier on given resource using dmi client") - public ResponseEntity<Object> getResourceDataFromDmi(final CmResourceAddress cmResourceAddress, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final String authorization) { + public Mono<ResponseEntity<Object>> getResourceDataFromDmi(final CmResourceAddress cmResourceAddress, + final String options, + final String topic, + final String requestId, + final String authorization) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.cmHandleId()); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); - final String dmiResourceDataUrl = getDmiRequestUrl(cmResourceAddress.datastoreName(), - cmResourceAddress.cmHandleId(), cmResourceAddress.resourceIdentifier(), optionsParamInQuery, - topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, authorization); + final String dmiUrl = getDmiResourceDataUrl(cmResourceAddress.datastoreName(), yangModelCmHandle, + cmResourceAddress.resourceIdentifier(), options, topic); + return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, dmiUrl, jsonRequestBody, READ, authorization); } /** * This method fetches all the resource data from operational data store for given cm handle * identifier using dmi client. + * Note: this method is only used for DataSync * - * @param dataStoreName data store name * @param cmHandleId network resource identifier * @param requestId requestId for async responses * @return {@code ResponseEntity} response entity */ - public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName, - final String cmHandleId, - final String requestId) { + public ResponseEntity<Object> getAllResourceDataFromDmi(final String cmHandleId, final String requestId) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, - yangModelCmHandle); - final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/", - null, null, - yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, null); + + final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); + final String dmiUrl = + getDmiResourceDataUrl(PASSTHROUGH_OPERATIONAL.getDatastoreName(), yangModelCmHandle, "/", null, null); + return dmiRestClient.synchronousPostOperationWithJsonData(DATA, dmiUrl, jsonRequestBody, READ, null); } /** @@ -137,13 +132,13 @@ public class DmiDataOperations extends DmiOperations { = getDistinctCmHandleIdsFromDataOperationRequest(dataOperationRequest); final Collection<YangModelCmHandle> yangModelCmHandles - = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); + = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); final Map<String, List<DmiDataOperation>> operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(topicParamInQuery, requestId, dataOperationRequest, yangModelCmHandles); - buildDataOperationRequestUrlAndSendToDmiService(topicParamInQuery, requestId, operationsOutPerDmiServiceName, + buildDataOperationRequestUrlAndSendToDmiService(requestId, topicParamInQuery, operationsOutPerDmiServiceName, authorization); } @@ -166,14 +161,15 @@ public class DmiDataOperations extends DmiOperations { final String dataType, final String authorization) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType, - yangModelCmHandle); - final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId, - null, null, - yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType, authorization); + + final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType, + yangModelCmHandle); + final String dmiUrl = getDmiResourceDataUrl(PASSTHROUGH_RUNNING.getDatastoreName(), + yangModelCmHandle, resourceId, null, null); + return dmiRestClient.synchronousPostOperationWithJsonData(DATA, dmiUrl, jsonRequestBody, + operationType, authorization); } private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { @@ -190,30 +186,28 @@ public class DmiDataOperations extends DmiOperations { .requestId(requestId) .data(requestData) .dataType(dataType) + .moduleSetTag(yangModelCmHandle.getModuleSetTag()) .build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); return jsonObjectMapper.asJsonString(dmiRequestBody); } - private String getDmiRequestUrl(final String dataStoreName, - final String cmHandleId, - final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery, - final String dmiServiceName) { - return dmiServiceUrlBuilder.getDmiDatastoreUrl( - dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, - topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, - cmHandleId)); - } - - private String getDmiServiceDataOperationRequestUrl(final String dmiServiceName, - final String topicParamInQuery, - final String requestId) { - final MultiValueMap<String, String> dataOperationRequestQueryParams = dmiServiceUrlBuilder - .getDataOperationRequestQueryParams(topicParamInQuery, requestId); - return dmiServiceUrlBuilder.getDataOperationRequestUrl(dataOperationRequestQueryParams, - dmiServiceUrlBuilder.populateDataOperationRequestUriVariables(dmiServiceName)); + private String getDmiResourceDataUrl(final String datastoreName, + final YangModelCmHandle yangModelCmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery) { + final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + return DmiServiceUrlBuilder.newInstance() + .pathSegment("ch") + .variablePathSegment("cmHandleId", yangModelCmHandle.getId()) + .pathSegment("data") + .pathSegment("ds") + .variablePathSegment("datastore", datastoreName) + .queryParameter("resourceIdentifier", resourceIdentifier) + .queryParameter("options", optionsParamInQuery) + .queryParameter("topic", topicParamInQuery) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); } private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, @@ -226,66 +220,78 @@ public class DmiDataOperations extends DmiOperations { } private static Set<String> getDistinctCmHandleIdsFromDataOperationRequest(final DataOperationRequest - dataOperationRequest) { + dataOperationRequest) { return dataOperationRequest.getDataOperationDefinitions().stream() .flatMap(dataOperationDefinition -> dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet()); } - private void buildDataOperationRequestUrlAndSendToDmiService(final String topicParamInQuery, - final String requestId, + private void buildDataOperationRequestUrlAndSendToDmiService(final String requestId, + final String topicParamInQuery, final Map<String, List<DmiDataOperation>> - groupsOutPerDmiServiceName, + groupsOutPerDmiServiceName, final String authorization) { - groupsOutPerDmiServiceName.forEach((dmiServiceName, dmiDataOperationRequestBodies) -> { - final String dmiDataOperationResourceUrl = - getDmiServiceDataOperationRequestUrl(dmiServiceName, topicParamInQuery, requestId); - sendDataOperationRequestToDmiService(dmiDataOperationResourceUrl, dmiDataOperationRequestBodies, - authorization); - }); + Flux.fromIterable(groupsOutPerDmiServiceName.entrySet()) + .flatMap(dmiDataOperationsByDmiServiceName -> { + final String dmiServiceName = dmiDataOperationsByDmiServiceName.getKey(); + final String dmiUrl = buildDmiServiceUrl(dmiServiceName, requestId, topicParamInQuery); + final List<DmiDataOperation> dmiDataOperationRequestBodies + = dmiDataOperationsByDmiServiceName.getValue(); + return sendDataOperationRequestToDmiService(dmiUrl, dmiDataOperationRequestBodies, authorization); + }) + .subscribe(); } - private void sendDataOperationRequestToDmiService(final String dataOperationResourceUrl, - final List<DmiDataOperation> dmiDataOperationRequestBodies, - final String authorization) { + private String buildDmiServiceUrl(final String dmiServiceName, final String requestId, + final String topicParamInQuery) { + return DmiServiceUrlBuilder.newInstance() + .pathSegment("data") + .queryParameter("requestId", requestId) + .queryParameter("topic", topicParamInQuery) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); + } + + private Mono<Void> sendDataOperationRequestToDmiService(final String dmiUrl, + final List<DmiDataOperation> dmiDataOperationRequestBodies, + final String authorization) { + final String dmiDataOperationRequestAsJsonString + = createDmiDataOperationRequestAsJsonString(dmiDataOperationRequestBodies); + return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, dmiUrl, dmiDataOperationRequestAsJsonString, + READ, authorization) + .then() + .onErrorResume(DmiClientRequestException.class, dmiClientRequestException -> { + handleTaskCompletionException(dmiClientRequestException, dmiUrl, dmiDataOperationRequestBodies); + return Mono.empty(); + }); + } + + private String createDmiDataOperationRequestAsJsonString( + final List<DmiDataOperation> dmiDataOperationRequestBodies) { final DmiDataOperationRequest dmiDataOperationRequest = DmiDataOperationRequest.builder() - .operations(dmiDataOperationRequestBodies).build(); - final String dmiDataOperationRequestAsJsonString = - jsonObjectMapper.asJsonString(dmiDataOperationRequest); - try { - dmiRestClient.postOperationWithJsonData(dataOperationResourceUrl, dmiDataOperationRequestAsJsonString, READ, - authorization); - } catch (final Exception exception) { - handleTaskCompletionException(exception, dataOperationResourceUrl, dmiDataOperationRequestBodies); - } + .operations(dmiDataOperationRequestBodies) + .build(); + return jsonObjectMapper.asJsonString(dmiDataOperationRequest); } - private void handleTaskCompletionException(final Throwable throwable, + private void handleTaskCompletionException(final DmiClientRequestException dmiClientRequestException, final String dataOperationResourceUrl, final List<DmiDataOperation> dmiDataOperationRequestBodies) { - if (throwable != null) { - final MultiValueMap<String, String> dataOperationResourceUrlParameters = - UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams(); - final String topicName = dataOperationResourceUrlParameters.get("topic").get(0); - final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0); + final MultiValueMap<String, String> dataOperationResourceUrlParameters = + UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams(); + final String topicName = dataOperationResourceUrlParameters.get("topic").get(0); + final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0); - final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>> - cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); + final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>> + cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); - dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> { - final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream() - .map(CmHandle::getId).toList(); - if (throwable.getCause() instanceof HttpClientRequestException) { - cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody, - Map.of(UNABLE_TO_READ_RESOURCE_DATA, cmHandleIds)); - } else { - cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody, - Map.of(DMI_SERVICE_NOT_RESPONDING, cmHandleIds)); - } - }); - ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId, - cmHandleIdsPerResponseCodesPerOperation); - } + dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> { + final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream() + .map(DmiOperationCmHandle::getId).toList(); + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody, + Map.of(dmiClientRequestException.getNcmpResponseStatus(), cmHandleIds)); + }); + ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId, + cmHandleIdsPerResponseCodesPerOperation); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index 798f6de81..f1fdbea7f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -21,6 +21,7 @@ package org.onap.cps.ncmp.api.impl.operations; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ; import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL; import com.google.gson.JsonArray; @@ -33,34 +34,27 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.impl.config.DmiProperties; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.YangResource; +import org.onap.cps.ncmp.api.inventory.models.YangResource; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; /** * Operations class for DMI Model. */ -@Component -public class DmiModelOperations extends DmiOperations { +@RequiredArgsConstructor +@Service +public class DmiModelOperations { - /** - * Constructor for {@code DmiOperations}. This method also manipulates url properties. - * - * @param dmiRestClient {@code DmiRestClient} - */ - public DmiModelOperations(final InventoryPersistence inventoryPersistence, - final JsonObjectMapper jsonObjectMapper, - final NcmpConfiguration.DmiProperties dmiProperties, - final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { - super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); - } + private final JsonObjectMapper jsonObjectMapper; + private final DmiProperties dmiProperties; + private final DmiRestClient dmiRestClient; /** * Retrieves module references. @@ -113,9 +107,12 @@ public class DmiModelOperations extends DmiOperations { final String jsonRequestBody, final String cmHandle, final String resourceName) { - final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, - OperationType.READ, null); + final String dmiUrl = DmiServiceUrlBuilder.newInstance() + .pathSegment("ch") + .variablePathSegment("cmHandleId", cmHandle) + .variablePathSegment("resourceName", resourceName) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); + return dmiRestClient.synchronousPostOperationWithJsonData(MODEL, dmiUrl, jsonRequestBody, READ, null); } private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperationCmHandle.java index 618da7454..1bf2b77dc 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperationCmHandle.java @@ -29,14 +29,21 @@ import lombok.Getter; @JsonInclude(JsonInclude.Include.NON_NULL) @Getter @Builder -public class CmHandle { +public class DmiOperationCmHandle { private String id; @JsonProperty("cmHandleProperties") private Map<String, String> dmiProperties; + private String moduleSetTag; - public static CmHandle buildCmHandleWithProperties(final String cmHandleId, - final Map<String, String> dmiProperties) { - return CmHandle.builder().id(cmHandleId).dmiProperties(dmiProperties).build(); + /** + * Builds Dmi Operation Cm Handle object with all its associated properties. + */ + public static DmiOperationCmHandle buildDmiOperationCmHandle(final String cmHandleId, + final Map<String, String> dmiProperties, + final String moduleSetTag) { + return DmiOperationCmHandle.builder().id(cmHandleId) + .dmiProperties(dmiProperties).moduleSetTag(moduleSetTag) + .build(); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java deleted file mode 100644 index c8d73eac6..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * 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.operations; - -import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; -import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class DmiOperations { - - protected final InventoryPersistence inventoryPersistence; - protected final JsonObjectMapper jsonObjectMapper; - protected final NcmpConfiguration.DmiProperties dmiProperties; - protected final DmiRestClient dmiRestClient; - protected final DmiServiceUrlBuilder dmiServiceUrlBuilder; - - String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) { - return dmiServiceUrlBuilder.getResourceDataBasePathUriBuilder() - .pathSegment("{resourceName}") - .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString(); - } - - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java index f1032f818..f10b4f920 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Map; import lombok.Builder; import lombok.Getter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @JsonInclude(JsonInclude.Include.NON_NULL) @Getter diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java index fa00d1a15..e863228ed 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java @@ -21,9 +21,7 @@ package org.onap.cps.ncmp.api.impl.operations; import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.Locale; import lombok.Getter; import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException; @@ -48,13 +46,6 @@ public enum OperationType { return String.valueOf(operationName); } - private static final Map<String, OperationType> operationNameToOperationEnum = new HashMap<>(); - - static { - Arrays.stream(OperationType.values()).forEach( - operationType -> operationNameToOperationEnum.put(operationType.getOperationName(), operationType)); - } - /** * From operation name get operation enum type. * @@ -62,10 +53,10 @@ public enum OperationType { * @return the operation enum type */ public static OperationType fromOperationName(final String operationName) { - final OperationType operationType = operationNameToOperationEnum.get(operationName); - if (null == operationType) { + try { + return OperationType.valueOf(operationName.toUpperCase(Locale.ENGLISH)); + } catch (final IllegalArgumentException e) { throw new InvalidOperationException(operationName + " is an invalid operation name"); } - return operationType; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManager.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManager.java index 9d65a66d8..d01acb8a3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManager.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManager.java @@ -26,9 +26,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig; import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; 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 d6d6fd6bc..c5098936c 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 @@ -24,11 +24,11 @@ import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -39,7 +39,7 @@ import org.springframework.stereotype.Service; public class DmiPluginWatchDog { private final DmiRestClient dmiRestClient; - private final NetworkCmProxyDataService networkCmProxyDataService; + private final CmHandleQueryService cmHandleQueryService; private final TrustLevelManager trustLevelManager; @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN) @@ -68,7 +68,7 @@ public class DmiPluginWatchDog { log.debug("The Dmi Plugin: {} has already the same trust level: {}", dmiServiceName, newDmiTrustLevel); } else { final Collection<String> cmHandleIds = - networkCmProxyDataService.getAllCmHandleIdsByDmiPluginIdentifier(dmiServiceName); + cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiServiceName); trustLevelManager.handleUpdateOfDmiTrustLevel(dmiServiceName, cmHandleIds, newDmiTrustLevel); } }); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java index 60f39fcea..7ddde643a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java @@ -27,9 +27,9 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.springframework.stereotype.Service; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java index 26e94866a..533e15b91 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class DmiServiceNameOrganizer { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index 04acaa5e9..aeeeb6430 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -21,166 +21,91 @@ package org.onap.cps.ncmp.api.impl.utils; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import lombok.RequiredArgsConstructor; +import lombok.NoArgsConstructor; import org.apache.logging.log4j.util.Strings; -import org.apache.logging.log4j.util.TriConsumer; -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; -import org.onap.cps.spi.utils.CpsValidator; -import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; -@Component -@RequiredArgsConstructor +@NoArgsConstructor public class DmiServiceUrlBuilder { - private final NcmpConfiguration.DmiProperties dmiProperties; - private final CpsValidator cpsValidator; + private static final String FIXED_PATH_SEGMENT = null; - /** - * This method creates the dmi service url. - * - * @param queryParams query param map as key,value pair - * @param uriVariables uri param map as key (placeholder),value pair - * @return {@code String} dmi service url as string - */ - public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams, - final Map<String, Object> uriVariables) { - return getUriComponentsBuilder(getResourceDataBasePathUriBuilder(), queryParams, uriVariables) - .buildAndExpand().toUriString(); - } + final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance(); + final Map<String, Object> pathSegments = new LinkedHashMap<>(); - /** - * This method builds data operation request url. - * - * @param dataoperationRequestQueryParams query param map as key, value pair - * @param dataoperationRequestUriVariables uri param map as key (placeholder), value pair - * @return {@code String} data operation request url as string - */ - public String getDataOperationRequestUrl(final MultiValueMap<String, String> dataoperationRequestQueryParams, - final Map<String, Object> dataoperationRequestUriVariables) { - return getDataOperationResourceDataBasePathUriBuilder() - .queryParams(dataoperationRequestQueryParams) - .uriVariables(dataoperationRequestUriVariables) - .buildAndExpand().toUriString(); + public static DmiServiceUrlBuilder newInstance() { + return new DmiServiceUrlBuilder(); } /** - * This method creates the dmi service url builder object with path variables. + * Add a fixed pathSegment to the URI. * - * @return {@code UriComponentsBuilder} dmi service url builder object + * @param pathSegment the path segment + * @return this builder */ - public UriComponentsBuilder getResourceDataBasePathUriBuilder() { - return UriComponentsBuilder.newInstance() - .path("{dmiServiceName}") - .pathSegment("{dmiBasePath}") - .pathSegment("v1") - .pathSegment("ch") - .pathSegment("{cmHandleId}"); + public DmiServiceUrlBuilder pathSegment(final String pathSegment) { + pathSegments.put(pathSegment, FIXED_PATH_SEGMENT); + return this; } /** - * This method creates the dmi service url builder object with path variables for data operation request. + * Add a variable pathSegment to the URI. + * Do NOT add { } braces. the builder will take care of that * - * @return {@code UriComponentsBuilder} dmi service url builder object + * @param pathSegment the name of the variable path segment (with { and } + * @param value the value to be insert in teh URI for the given variable path segment + * @return this builder */ - public UriComponentsBuilder getDataOperationResourceDataBasePathUriBuilder() { - return UriComponentsBuilder.newInstance() - .path("{dmiServiceName}") - .pathSegment("{dmiBasePath}") - .pathSegment("v1") - .pathSegment("data"); + public DmiServiceUrlBuilder variablePathSegment(final String pathSegment, final Object value) { + pathSegments.put(pathSegment, value); + return this; } /** - * This method populates uri variables. + * Add a query parameter to the URI. + * Do NOT encode as the builder wil take care of encoding * - * @param dataStoreName data store name - * @param dmiServiceName dmi service name - * @param cmHandleId cm handle id for dmi registration - * @return {@code String} dmi service url as string + * @param name the name of the variable + * @param value the value of the variable (only Strings are supported). + * + * @return this builder */ - public Map<String, Object> populateUriVariables(final String dataStoreName, - final String dmiServiceName, - final String cmHandleId) { - cpsValidator.validateNameCharacters(cmHandleId); - final Map<String, Object> uriVariables = new HashMap<>(); - final String dmiBasePath = dmiProperties.getDmiBasePath(); - uriVariables.put("dmiServiceName", dmiServiceName); - uriVariables.put("dmiBasePath", dmiBasePath); - uriVariables.put("cmHandleId", cmHandleId); - uriVariables.put("dataStore", dataStoreName); - return uriVariables; + public DmiServiceUrlBuilder queryParameter(final String name, final String value) { + if (Strings.isNotBlank(value)) { + uriComponentsBuilder.queryParam(name, value); + } + return this; } /** - * This method populates uri variables for data operation request. + * Build the URI as a correctly percentage-encoded String. + * + * @param dmiServiceName the name of the dmi service + * @param dmiBasePath the base path of the dmi service * - * @param dmiServiceName dmi service name - * @return {@code Map<String, Object>} uri variables as map + * @return URI as a string */ - public Map<String, Object> populateDataOperationRequestUriVariables(final String dmiServiceName) { + public String build(final String dmiServiceName, final String dmiBasePath) { + uriComponentsBuilder + .path("{dmiServiceName}") + .pathSegment("{dmiBasePath}") + .pathSegment("v1"); + final Map<String, Object> uriVariables = new HashMap<>(); - final String dmiBasePath = dmiProperties.getDmiBasePath(); uriVariables.put("dmiServiceName", dmiServiceName); uriVariables.put("dmiBasePath", dmiBasePath); - return uriVariables; - } - - /** - * This method is used to populate map from query params. - * - * @param resourceId unique id of response for valid topic - * @param optionsParamInQuery options into url param - * @param topicParamInQuery topic into url param - * @return all valid query params as map - */ - public MultiValueMap<String, String> populateQueryParams(final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery) { - final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); - getQueryParamConsumer().accept("resourceIdentifier", - resourceId, queryParams); - getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams); - if (Strings.isNotEmpty(topicParamInQuery)) { - getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); - } - return queryParams; - } - /** - * This method is used to populate map from query params for data operation request. - * - * @param topicParamInQuery topic into url param - * @param requestId unique id of response for valid topic - * @return all valid query params as map - */ - public MultiValueMap<String, String> getDataOperationRequestQueryParams(final String topicParamInQuery, - final String requestId) { - final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); - getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); - getQueryParamConsumer().accept("requestId", requestId, queryParams); - return queryParams; - } - - private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() { - return (paramName, paramValue, paramMap) -> { - if (Strings.isNotEmpty(paramValue)) { - paramMap.add(paramName, paramValue); + pathSegments.forEach((pathSegment, variablePathValue) -> { + if (variablePathValue == FIXED_PATH_SEGMENT) { + uriComponentsBuilder.pathSegment(pathSegment); + } else { + uriComponentsBuilder.pathSegment("{" + pathSegment + "}"); + uriVariables.put(pathSegment, variablePathValue); } - }; + }); + return uriComponentsBuilder.buildAndExpand(uriVariables).encode().toUriString(); } - private UriComponentsBuilder getUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder, - final MultiValueMap<String, String> queryParams, - final Map<String, Object> uriVariables) { - return uriComponentsBuilder - .pathSegment("data") - .pathSegment("ds") - .pathSegment("{dataStore}") - .queryParams(queryParams) - .uriVariables(uriVariables); - } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java index 4b016b37d..bb0c9a315 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java @@ -31,26 +31,25 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.events.EventsPublisher; import org.onap.cps.ncmp.api.NcmpResponseStatus; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.operations.CmHandle; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation; +import org.onap.cps.ncmp.api.impl.operations.DmiOperationCmHandle; import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer; import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.DataOperationDefinition; import org.onap.cps.ncmp.api.models.DataOperationRequest; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -@Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j public class ResourceDataOperationRequestUtils { private static final String UNKNOWN_SERVICE_NAME = null; @@ -81,6 +80,8 @@ public class ResourceDataOperationRequestUtils { final Map<String, String> dmiServiceNamesPerCmHandleId = getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName); + final Map<String, String> moduleSetTagPerCmHandle = getModuleSetTagPerCmHandleId(yangModelCmHandles); + for (final DataOperationDefinition dataOperationDefinitionIn : dataOperationRequestIn.getDataOperationDefinitions()) { final List<String> nonExistingCmHandleIds = new ArrayList<>(); @@ -97,9 +98,10 @@ public class ResourceDataOperationRequestUtils { } else { final DmiDataOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName, dataOperationDefinitionIn, dmiDataOperationsOutPerDmiServiceName); - final CmHandle cmHandle = CmHandle.buildCmHandleWithProperties(cmHandleId, - cmHandleIdProperties); - dmiBatchOperationOut.getCmHandles().add(cmHandle); + final DmiOperationCmHandle dmiOperationCmHandle = DmiOperationCmHandle + .buildDmiOperationCmHandle(cmHandleId, cmHandleIdProperties, + moduleSetTagPerCmHandle.get(cmHandleId)); + dmiBatchOperationOut.getCmHandles().add(dmiOperationCmHandle); } } } @@ -114,56 +116,12 @@ public class ResourceDataOperationRequestUtils { return dmiDataOperationsOutPerDmiServiceName; } - /** - * Handles the async task completion for an entire data, publishing errors to client topic on task failure. - * - * @param topicParamInQuery client given topic - * @param requestId unique identifier per request - * @param dataOperationRequest incoming data operation request details - * @param throwable error cause, or null if task completed with no exception - */ - public static void handleAsyncTaskCompletionForDataOperationsRequest( - final String topicParamInQuery, - final String requestId, - final DataOperationRequest dataOperationRequest, - final Throwable throwable) { - if (throwable == null) { - log.info("Data operations request {} completed.", requestId); - } else if (throwable instanceof TimeoutException) { - log.error("Data operations request {} timed out.", requestId); - ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery, - requestId, dataOperationRequest, NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING); - } else { - log.error("Data operations request {} failed.", requestId, throwable); - ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery, - requestId, dataOperationRequest, NcmpResponseStatus.UNKNOWN_ERROR); - } - } - - /** - * Creates data operation cloud event for when the entire data operation fails and publishes it to client topic. - * - * @param topicParamInQuery client given topic - * @param requestId unique identifier per request - * @param dataOperationRequestIn incoming data operation request details - * @param ncmpResponseStatus response code to be sent for all cm handle ids in all operations - */ - private static void publishErrorMessageToClientTopicForEntireOperation( - final String topicParamInQuery, - final String requestId, - final DataOperationRequest dataOperationRequestIn, - final NcmpResponseStatus ncmpResponseStatus) { - - final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>> - cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>(); - - for (final DataOperationDefinition dataOperationDefinitionIn : - dataOperationRequestIn.getDataOperationDefinitions()) { - cmHandleIdsPerResponseCodesPerOperation.add( - DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn), - Map.of(ncmpResponseStatus, dataOperationDefinitionIn.getCmHandleIds())); - } - publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation); + private static Map<String, String> getModuleSetTagPerCmHandleId( + final Collection<YangModelCmHandle> yangModelCmHandles) { + final Map<String, String> moduleSetTagPerCmHandle = new HashMap<>(yangModelCmHandles.size()); + yangModelCmHandles.forEach(yangModelCmHandle -> + moduleSetTagPerCmHandle.put(yangModelCmHandle.getId(), yangModelCmHandle.getModuleSetTag())); + return moduleSetTagPerCmHandle; } /** @@ -182,6 +140,8 @@ public class ResourceDataOperationRequestUtils { final CloudEvent dataOperationCloudEvent = DataOperationEventCreator.createDataOperationEvent(clientTopic, requestId, cmHandleIdsPerResponseCodesPerOperation); final EventsPublisher<CloudEvent> eventsPublisher = CpsApplicationContext.getCpsBean(EventsPublisher.class); + log.warn("publishing error message to client topic: {} ,requestId: {}, data operation cloud event id: {}", + clientTopic, requestId, dataOperationCloudEvent.getId()); eventsPublisher.publishCloudEvent(clientTopic, requestId, dataOperationCloudEvent); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java new file mode 100644 index 000000000..0ff59c131 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -0,0 +1,208 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 highstreet technologies GmbH + * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.inventory; + +import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCmHandleQueryParameters; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions; +import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; +import org.onap.cps.spi.model.ModuleDefinition; +import org.onap.cps.spi.model.ModuleReference; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class NetworkCmProxyInventoryFacade { + + private final CmHandleRegistrationService cmHandleRegistrationService; + private final CmHandleQueryService cmHandleQueryService; + private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService; + private final InventoryPersistence inventoryPersistence; + private final JsonObjectMapper jsonObjectMapper; + + /** + * Registration of Created, Removed, Updated or Upgraded CM Handles. + * + * @param dmiPluginRegistration Dmi Plugin Registration details + * @return dmiPluginRegistrationResponse + */ + + public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( + final DmiPluginRegistration dmiPluginRegistration) { + return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration); + } + + /** + * Get all cm handle IDs by DMI plugin identifier. + * + * @param dmiPluginIdentifier DMI plugin identifier + * @return collection of cm handle IDs + */ + public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) { + return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); + } + + /** + * Get all cm handle IDs by various properties. + * + * @param cmHandleQueryServiceParameters cm handle query parameters + * @return collection of cm handle IDs + */ + public Collection<String> executeParameterizedCmHandleIdSearch( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); + return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters); + } + + + /** + * Retrieve module references for the given cm handle. + * + * @param cmHandleId cm handle identifier + * @return a collection of modules names and revisions + */ + public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) { + return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId); + } + + /** + * Retrieve module definitions for the given cm handle. + * + * @param cmHandleId cm handle identifier + * @return a collection of module definition (moduleName, revision and yang resource content) + */ + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { + return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); + } + + /** + * Get module definitions for the given parameters. + * + * @param cmHandleId cm-handle identifier + * @param moduleName module name + * @param moduleRevision the revision of the module + * @return list of module definitions (module name, revision, yang resource content) + */ + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId, + final String moduleName, + final String moduleRevision) { + return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision); + } + + /** + * Retrieve cm handles with details for the given query parameters. + * + * @param cmHandleQueryApiParameters cm handle query parameters + * @return cm handles with details + */ + public Collection<NcmpServiceCmHandle> executeCmHandleSearch( + final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); + return parameterizedCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters); + } + + /** + * Retrieve cm handle ids for the given query parameters. + * + * @param cmHandleQueryApiParameters cm handle query parameters + * @return cm handle ids + */ + public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); + return parameterizedCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); + } + + /** + * 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 dataSyncEnabledTargetValue data sync enabled flag + */ + public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) { + cmHandleRegistrationService.setDataSyncEnabled(cmHandleId, dataSyncEnabledTargetValue); + } + + /** + * Retrieve cm handle details for a given cm handle. + * + * @param cmHandleId cm handle identifier + * @return cm handle details + */ + public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) { + return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle( + inventoryPersistence.getYangModelCmHandle(cmHandleId)); + } + + /** + * Get cm handle public properties for a given cm handle id. + * + * @param cmHandleId cm handle identifier + * @return cm handle public properties + */ + public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) { + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); + final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties(); + final Map<String, String> cmHandlePublicProperties = new HashMap<>(); + YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); + return cmHandlePublicProperties; + } + + /** + * Get cm handle composite state for a given cm handle id. + * + * @param cmHandleId cm handle identifier + * @return cm handle state + */ + public CompositeState getCmHandleCompositeState(final String cmHandleId) { + return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); + } + + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryApiParameters.java index dd8dcd60a..596fb94a3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryApiParameters.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryServiceParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryServiceParameters.java index 5eeafaca3..13915918e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryServiceParameters.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryServiceParameters.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponse.java index 52b8d6926..7523f77af 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponse.java @@ -19,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR; @@ -31,7 +31,7 @@ import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NcmpResponseStatus; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; @Data @Builder diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeState.java index 5b88f560e..ca4fde2b6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeState.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -29,6 +29,9 @@ import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory; /** * State Model to store state corresponding to the Yang resource dmi-registry model. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilder.java index 2fbe2b2f7..59d7aa2b4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilder.java @@ -19,11 +19,14 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.api.inventory.models; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState.LockReason; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational; +import org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores; +import org.onap.cps.ncmp.api.inventory.models.CompositeState.LockReason; +import org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational; +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory; import org.onap.cps.spi.model.DataNode; public class CompositeStateBuilder { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/ConditionApiProperties.java index 5cb2ed376..61d9f4a25 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/ConditionApiProperties.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistration.java index 7d6a8e140..a5002e790 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistration.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistrationResponse.java index ee034176e..736ca620d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistrationResponse.java @@ -19,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import java.util.Collections; import java.util.List; @@ -33,4 +33,4 @@ public class DmiPluginRegistrationResponse { private List<CmHandleRegistrationResponse> updatedCmHandles = Collections.emptyList(); private List<CmHandleRegistrationResponse> removedCmHandles = Collections.emptyList(); private List<CmHandleRegistrationResponse> upgradedCmHandles = Collections.emptyList(); -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/NcmpServiceCmHandle.java index 676eebc4d..69b33a16f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/NcmpServiceCmHandle.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; @@ -27,7 +27,6 @@ import java.util.Map; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel; import org.springframework.validation.annotation.Validated; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/UpgradedCmHandles.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/UpgradedCmHandles.java index 61cd99ac8..9f48ae968 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/UpgradedCmHandles.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/UpgradedCmHandles.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Collections; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/YangResource.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/YangResource.java index 7975777aa..1c0ca5d8c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/YangResource.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/YangResource.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models; +package org.onap.cps.ncmp.api.inventory.models; import lombok.Data; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/InvalidTopicException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/InvalidTopicException.java new file mode 100644 index 000000000..fcf2a28cc --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/InvalidTopicException.java @@ -0,0 +1,40 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.exceptions; + +import lombok.Getter; + +public class InvalidTopicException extends RuntimeException { + + @Getter + final String details; + + /** + * Constructor. + * + * @param message the error message + * @param details the error details + */ + public InvalidTopicException(final String message, final String details) { + super(message); + this.details = details; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/NoAlternateIdMatchFoundException.java index 2e6cd3308..510a6f51a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/NoAlternateIdMatchFoundException.java @@ -18,22 +18,23 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.exception; +package org.onap.cps.ncmp.exceptions; import java.io.Serial; +import org.onap.cps.ncmp.api.impl.exception.NcmpException; -public class NoAlternateIdParentFoundException extends NcmpException { +public class NoAlternateIdMatchFoundException extends NcmpException { @Serial private static final long serialVersionUID = -2412915490233422945L; - private static final String ALTERNATE_ID_NOT_FOUND = "No matching (parent) cm handle found using alternate ids"; + private static final String ALTERNATE_ID_NOT_FOUND = "No matching cm handle found using alternate ids"; /** * Constructor. * * @param cpsPath datanode cpsPath */ - public NoAlternateIdParentFoundException(final String cpsPath) { + public NoAlternateIdMatchFoundException(final String cpsPath) { super(ALTERNATE_ID_NOT_FOUND, String.format("cannot find a datanode with alternate id %s", cpsPath)); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/OperationNotSupportedException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/OperationNotSupportedException.java new file mode 100644 index 000000000..d75c0bd47 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/OperationNotSupportedException.java @@ -0,0 +1,32 @@ +/* + * ============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.exceptions; + +public class OperationNotSupportedException extends RuntimeException { + /** + * Instantiates a new not implemented operation exception. + * + * @param message the message + */ + public OperationNotSupportedException(final String message) { + super(message); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/PayloadTooLargeException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/PayloadTooLargeException.java new file mode 100644 index 000000000..dc7057af7 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/PayloadTooLargeException.java @@ -0,0 +1,31 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.exceptions; + +public class PayloadTooLargeException extends RuntimeException { + + /** + * Instantiates a new payload too large exception. + */ + public PayloadTooLargeException(final String message) { + super(message); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/DataJobServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImpl.java index b4377b84f..56ed6e30d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/DataJobServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImpl.java @@ -18,17 +18,29 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl; +package org.onap.cps.ncmp.impl.datajobs; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.DataJobService; -import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata; -import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest; -import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest; +import org.onap.cps.ncmp.api.datajobs.DataJobService; +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata; +import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest; +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest; +import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation; +import org.onap.cps.ncmp.api.datajobs.models.ProducerKey; +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse; +import org.springframework.stereotype.Service; @Slf4j +@Service +@RequiredArgsConstructor public class DataJobServiceImpl implements DataJobService { + private final DmiSubJobRequestHandler dmiSubJobClient; + private final WriteRequestExaminer writeRequestExaminer; + @Override public void readDataJob(final String dataJobId, final DataJobMetadata dataJobMetadata, final DataJobReadRequest dataJobReadRequest) { @@ -36,8 +48,13 @@ public class DataJobServiceImpl implements DataJobService { } @Override - public void writeDataJob(final String dataJobId, final DataJobMetadata dataJobMetadata, - final DataJobWriteRequest dataJobWriteRequest) { + public List<SubJobWriteResponse> writeDataJob(final String dataJobId, final DataJobMetadata dataJobMetadata, + final DataJobWriteRequest dataJobWriteRequest) { log.info("data job id for write operation is: {}", dataJobId); + + final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey = + writeRequestExaminer.splitDmiWriteOperationsFromRequest(dataJobId, dataJobWriteRequest); + + return dmiSubJobClient.sendRequestsToDmi(dataJobId, dataJobMetadata, dmiWriteOperationsPerProducerKey); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java new file mode 100644 index 000000000..8027e69d5 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.datajobs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata; +import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation; +import org.onap.cps.ncmp.api.datajobs.models.ProducerKey; +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest; +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse; +import org.onap.cps.ncmp.api.impl.client.DmiRestClient; +import org.onap.cps.ncmp.api.impl.config.DmiProperties; +import org.onap.cps.ncmp.api.impl.operations.OperationType; +import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; +import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DmiSubJobRequestHandler { + + private final DmiRestClient dmiRestClient; + private final DmiProperties dmiProperties; + private final JsonObjectMapper jsonObjectMapper; + static final String NO_AUTH_HEADER = null; + + /** + * Sends sub-job write requests to the DMI Plugin. + * + * @param dataJobId data ojb identifier + * @param dataJobMetadata the data job's metadata + * @param dmiWriteOperationsPerProducerKey a collection of write requests per producer key. + * @return a list of sub-job write responses + */ + public List<SubJobWriteResponse> sendRequestsToDmi(final String dataJobId, final DataJobMetadata dataJobMetadata, + final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey) { + final List<SubJobWriteResponse> subJobWriteResponses = new ArrayList<>(dmiWriteOperationsPerProducerKey.size()); + dmiWriteOperationsPerProducerKey.forEach((producerKey, dmi3ggpWriteOperations) -> { + final SubJobWriteRequest subJobWriteRequest = new SubJobWriteRequest(dataJobMetadata.dataAcceptType(), + dataJobMetadata.dataContentType(), dataJobId, dmi3ggpWriteOperations); + + final String dmiResourceUrl = getDmiResourceUrl(dataJobId, producerKey); + final ResponseEntity<Object> responseEntity = dmiRestClient.synchronousPostOperationWithJsonData( + RequiredDmiService.DATA, + dmiResourceUrl, + jsonObjectMapper.asJsonString(subJobWriteRequest), + OperationType.CREATE, + NO_AUTH_HEADER); + final SubJobWriteResponse subJobWriteResponse = (SubJobWriteResponse) responseEntity.getBody(); + log.debug("Sub job write response: {}", subJobWriteResponse); + subJobWriteResponses.add(subJobWriteResponse); + }); + return subJobWriteResponses; + } + + private String getDmiResourceUrl(final String dataJobId, final ProducerKey producerKey) { + return DmiServiceUrlBuilder.newInstance().pathSegment("writeJob").variablePathSegment("requestId", dataJobId) + .build(producerKey.dmiServiceName(), dmiProperties.getDmiBasePath()); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java new file mode 100644 index 000000000..0e51f0c44 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java @@ -0,0 +1,109 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.datajobs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest; +import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation; +import org.onap.cps.ncmp.api.datajobs.models.ProducerKey; +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; +import org.onap.cps.spi.model.DataNode; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WriteRequestExaminer { + + private final AlternateIdMatcher alternateIdMatcher; + private static final String PATH_SEPARATOR = "/"; + + /** + * Splitting incoming data job write request into Dmi Write Operations by ProducerKey. + * + * @param dataJobId data job identifier + * @param dataJobWriteRequest incoming data job write request + * @return {@code Map} map of Dmi Write Operations per Producer Key + */ + public Map<ProducerKey, List<DmiWriteOperation>> splitDmiWriteOperationsFromRequest( + final String dataJobId, + final DataJobWriteRequest dataJobWriteRequest) { + final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey = new HashMap<>(); + for (final WriteOperation writeOperation : dataJobWriteRequest.data()) { + examineWriteOperation(dataJobId, dmiWriteOperationsPerProducerKey, writeOperation); + } + return dmiWriteOperationsPerProducerKey; + } + + private void examineWriteOperation(final String dataJobId, + final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey, + final WriteOperation writeOperation) { + log.debug("data job id for write operation is: {}", dataJobId); + final DataNode dataNode = alternateIdMatcher + .getCmHandleDataNodeByLongestMatchingAlternateId(writeOperation.path(), PATH_SEPARATOR); + + final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, dataNode); + + final ProducerKey producerKey = createProducerKey(dataNode); + final List<DmiWriteOperation> dmiWriteOperations; + if (dmiWriteOperationsPerProducerKey.containsKey(producerKey)) { + dmiWriteOperations = dmiWriteOperationsPerProducerKey.get(producerKey); + } else { + dmiWriteOperations = new ArrayList<>(); + dmiWriteOperationsPerProducerKey.put(producerKey, dmiWriteOperations); + } + dmiWriteOperations.add(dmiWriteOperation); + } + + private ProducerKey createProducerKey(final DataNode dataNode) { + return new ProducerKey((String) dataNode.getLeaves().get("dmi-service-name"), + (String) dataNode.getLeaves().get("data-producer-identifier")); + } + + private DmiWriteOperation createDmiWriteOperation(final WriteOperation writeOperation, + final DataNode dataNode) { + return new DmiWriteOperation( + writeOperation.path(), + writeOperation.op(), + (String) dataNode.getLeaves().get("module-set-tag"), + writeOperation.value(), + writeOperation.operationId(), + getPrivatePropertiesFromDataNode(dataNode)); + } + + private Map<String, String> getPrivatePropertiesFromDataNode(final DataNode dataNode) { + final YangModelCmHandle yangModelCmHandle = YangDataConverter.convertCmHandleToYangModel(dataNode); + final Map<String, String> cmHandleDmiProperties = new LinkedHashMap<>(); + yangModelCmHandle.getDmiProperties() + .forEach(dmiProperty -> cmHandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue())); + return cmHandleDmiProperties; + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidator.java index 2ef97cab5..08954024b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidator.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils; +package org.onap.cps.ncmp.impl.inventory; import com.google.common.base.Strings; import java.util.Collection; @@ -26,12 +26,12 @@ import java.util.Map; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.spi.exceptions.DataValidationException; @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class RestQueryParametersValidator { +public class CmHandleQueryParametersValidator { /** * Validate query parameters. @@ -55,7 +55,7 @@ public class RestQueryParametersValidator { "Empty 'conditionsParameters' - please supply a valid condition parameter."); } cmHandleQueryParameter.getConditionParameters().forEach( - RestQueryParametersValidator::validateConditionParameter + CmHandleQueryParametersValidator::validateConditionParameter ); } ); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueries.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java index 81467dbb3..795d7c9c4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueries.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java @@ -18,26 +18,27 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; import java.util.Collection; import java.util.List; import java.util.Map; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; -public interface CmHandleQueries { +public interface CmHandleQueryService { /** - * Query CmHandles based on additional (private) properties. + * Query Cm Handles based on additional (private) properties. * * @param additionalPropertyQueryPairs private properties for query - * @return Ids of CmHandles which have these private properties + * @return Ids of Cm Handles which have these private properties */ Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs); /** - * Query CmHandles based on public properties. + * Query Cm Handles based on public properties. * * @param publicPropertyQueryPairs public properties for query * @return CmHandles which have these public properties @@ -45,10 +46,10 @@ public interface CmHandleQueries { Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs); /** - * Query CmHandles based on Trust Level. + * Query Cm Handles based on Trust Level. * * @param trustLevelPropertyQueryPairs trust level properties for query - * @return CmHandles which have desired trust level + * @return Ids of Cm Handles which have desired trust level */ Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs); @@ -56,7 +57,7 @@ public interface CmHandleQueries { * Method which returns cm handles by the cm handles state. * * @param cmHandleState cm handle state - * @return a list of cm handles + * @return a list of data nodes representing the cm handles. */ List<DataNode> queryCmHandlesByState(CmHandleState cmHandleState); @@ -90,7 +91,7 @@ public interface CmHandleQueries { * Method which returns cm handles by the operational sync state of cm handle. * * @param dataStoreSyncState sync state - * @return a list of cm handles + * @return a list of data nodes representing the cm handles. */ List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState); @@ -98,7 +99,8 @@ public interface CmHandleQueries { * Get all cm handles ids by DMI plugin identifier. * * @param dmiPluginIdentifier DMI plugin identifier - * @return collection of cm handles + * @return collection of cm handle ids */ Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier); + } 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/impl/inventory/CmHandleQueryServiceImpl.java index 6cffb4d27..4c2f93a95 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/impl/inventory/CmHandleQueryServiceImpl.java @@ -19,11 +19,11 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; -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.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; @@ -35,8 +35,10 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig; -import org.onap.cps.ncmp.api.impl.inventory.enums.PropertyType; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.ModelledDmiServiceLeaves; +import org.onap.cps.ncmp.impl.inventory.models.PropertyType; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; @@ -46,7 +48,7 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor @Component -public class CmHandleQueriesImpl implements CmHandleQueries { +public class CmHandleQueryServiceImpl implements CmHandleQueryService { private static final String DESCENDANT_PATH = "//"; private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; 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/impl/inventory/CmHandleRegistrationService.java index 6aa09767b..f10ccd4ae 100755..100644 --- 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/impl/inventory/CmHandleRegistrationService.java @@ -1,7 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 highstreet technologies GmbH - * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada * Modifications Copyright (C) 2023 TechMahindra Ltd. @@ -22,17 +21,16 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl; +package org.onap.cps.ncmp.impl.inventory; import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_READY; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID; -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE; -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; -import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCmHandleQueryParameters; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE; import com.google.common.collect.Lists; import com.hazelcast.map.IMap; @@ -49,70 +47,52 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsDataService; -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig; import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder; -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateUtils; -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleOperationsUtils; -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; -import org.onap.cps.ncmp.api.impl.operations.OperationType; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager; import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker; -import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions; -import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; -import org.onap.cps.ncmp.api.models.CmResourceAddress; -import org.onap.cps.ncmp.api.models.DataOperationRequest; -import org.onap.cps.ncmp.api.models.DmiPluginRegistration; -import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; -import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.sync.ModuleOperationsUtils; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; -import org.onap.cps.spi.model.ModuleDefinition; -import org.onap.cps.spi.model.ModuleReference; -import org.onap.cps.utils.JsonObjectMapper; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor -public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService { +public class CmHandleRegistrationService { private static final int DELETE_BATCH_SIZE = 100; - private final JsonObjectMapper jsonObjectMapper; - private final DmiDataOperations dmiDataOperations; - private final NetworkCmProxyDataServicePropertyHandler networkCmProxyDataServicePropertyHandler; + + private final CmHandleRegistrationServicePropertyHandler cmHandleRegistrationServicePropertyHandler; private final InventoryPersistence inventoryPersistence; - private final CmHandleQueries cmHandleQueries; - private final NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService; - private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler; private final CpsDataService cpsDataService; + private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler; private final IMap<String, Object> moduleSyncStartedOnCmHandles; @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN) private final Map<String, TrustLevel> trustLevelPerDmiPlugin; - private final TrustLevelManager trustLevelManager; + private final AlternateIdChecker alternateIdChecker; - @Override + /** + * Registration of Created, Removed, Updated or Upgraded CM Handles. + * + * @param dmiPluginRegistration Dmi Plugin Registration details + * @return dmiPluginRegistrationResponse + */ public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( final DmiPluginRegistration dmiPluginRegistration) { @@ -132,95 +112,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService return dmiPluginRegistrationResponse; } - @Override - public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final String authorization) { - final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, - optionsParamInQuery, - topicParamInQuery, - requestId, - authorization); - return responseEntity.getBody(); - } - - @Override - public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress, - final FetchDescendantsOption fetchDescendantsOption) { - return cpsDataService.getDataNodes(cmResourceAddress.datastoreName(), - cmResourceAddress.cmHandleId(), - cmResourceAddress.resourceIdentifier(), - fetchDescendantsOption).iterator().next(); - } - - @Override - public void executeDataOperationForCmHandles(final String topicParamInQuery, - final DataOperationRequest dataOperationRequest, - final String requestId, - final String authorization) { - dmiDataOperations.requestResourceDataFromDmi(topicParamInQuery, dataOperationRequest, requestId, - authorization); - } - - @Override - public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId, - final String resourceIdentifier, - final OperationType operationType, - final String requestData, - final String dataType, - final String authorization) { - return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, - operationType, requestData, dataType, authorization); - } - - @Override - public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) { - return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId); - } - - @Override - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { - return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); - } - - @Override - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId, - final String moduleName, - final String moduleRevision) { - return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision); - } - - /** - * Retrieve cm handles with details for the given query parameters. - * - * @param cmHandleQueryApiParameters cm handle query parameters - * @return cm handles with details - */ - @Override - public Collection<NcmpServiceCmHandle> executeCmHandleSearch( - final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( - cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return networkCmProxyCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters); - } - - /** - * Retrieve cm handle ids for the given query parameters. - * - * @param cmHandleQueryApiParameters cm handle query parameters - * @return cm handle ids - */ - @Override - public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( - cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return networkCmProxyCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); - } - /** * 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. @@ -228,7 +119,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService * @param cmHandleId cm handle id * @param dataSyncEnabledTargetValue data sync enabled flag */ - @Override public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) { final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); if (dataSyncEnabledTargetValue.equals(compositeState.getDataSyncEnabled())) { @@ -252,70 +142,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } } - /** - * Get all cm handle IDs by DMI plugin identifier. - * - * @param dmiPluginIdentifier DMI plugin identifier - * @return set of cm handle IDs - */ - @Override - public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) { - return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); - } - - /** - * Get all cm handle IDs by various properties. - * - * @param cmHandleQueryServiceParameters cm handle query parameters - * @return set of cm handle IDs - */ - @Override - public Collection<String> executeCmHandleIdSearchForInventory( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); - return networkCmProxyCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters); - } - - /** - * Retrieve cm handle details for a given cm handle. - * - * @param cmHandleId cm handle identifier - * @return cm handle details - */ - @Override - public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) { - return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle( - inventoryPersistence.getYangModelCmHandle(cmHandleId)); - } - - /** - * Get cm handle public properties for a given cm handle id. - * - * @param cmHandleId cm handle identifier - * @return cm handle public properties - */ - @Override - public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) { - final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); - final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties(); - final Map<String, String> cmHandlePublicProperties = new HashMap<>(); - YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); - return cmHandlePublicProperties; - } - - /** - * Get cm handle composite state for a given cm handle id. - * - * @param cmHandleId cm handle identifier - * @return cm handle state - */ - @Override - public CompositeState getCmHandleCompositeState(final String cmHandleId) { - return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); - } - protected void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration, - final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { + final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { final List<String> tobeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles(); final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(tobeRemovedCmHandleIds.size()); @@ -348,7 +176,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } protected void processCreatedCmHandles(final DmiPluginRegistration dmiPluginRegistration, - final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { + final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { final List<NcmpServiceCmHandle> ncmpServiceCmHandles = dmiPluginRegistration.getCreatedCmHandles(); final List<CmHandleRegistrationResponse> failedCmHandleRegistrationResponses = new ArrayList<>(); @@ -378,8 +206,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } protected void processUpdatedCmHandles(final DmiPluginRegistration dmiPluginRegistration, - final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { - dmiPluginRegistrationResponse.setUpdatedCmHandles(networkCmProxyDataServicePropertyHandler + final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { + dmiPluginRegistrationResponse.setUpdatedCmHandles(cmHandleRegistrationServicePropertyHandler .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles())); } @@ -405,67 +233,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } } else { cmHandleUpgradeResponses.add( - CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_READY)); + CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_READY)); } } catch (final DataNodeNotFoundException dataNodeNotFoundException) { log.error("Unable to find data node for cm handle id : {} , caused by : {}", - cmHandleId, dataNodeNotFoundException.getMessage()); + cmHandleId, dataNodeNotFoundException.getMessage()); cmHandleUpgradeResponses.add( - CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND)); + CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND)); } catch (final DataValidationException dataValidationException) { log.error("Unable to upgrade cm handle id: {}, caused by : {}", - cmHandleId, dataValidationException.getMessage()); + cmHandleId, dataValidationException.getMessage()); cmHandleUpgradeResponses.add( - CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID)); + CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID)); } } cmHandleUpgradeResponses.addAll(upgradeCmHandles(acceptedCmHandleStatePerCmHandle)); dmiPluginRegistrationResponse.setUpgradedCmHandles(cmHandleUpgradeResponses); } - private Collection<String> checkAlternateIds( - final List<NcmpServiceCmHandle> cmHandlesToBeCreated, - final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses) { - final Collection<String> rejectedCmHandleIds = alternateIdChecker - .getIdsOfCmHandlesWithRejectedAlternateId(cmHandlesToBeCreated, AlternateIdChecker.Operation.CREATE); - cmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponses( - rejectedCmHandleIds, ALTERNATE_ID_ALREADY_ASSOCIATED)); - return rejectedCmHandleIds; - } - - private List<String> persistCmHandlesWithState(final DmiPluginRegistration dmiPluginRegistration, - final DmiPluginRegistrationResponse dmiPluginRegistrationResponse, - final List<NcmpServiceCmHandle> cmHandlesToBeCreated, - final Collection<String> rejectedCmHandleIds) { - final List<String> succeededCmHandleIds = new ArrayList<>(cmHandlesToBeCreated.size()); - final List<YangModelCmHandle> yangModelCmHandlesToRegister = new ArrayList<>(cmHandlesToBeCreated.size()); - final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = - new ArrayList<>(cmHandlesToBeCreated.size()); - for (final NcmpServiceCmHandle ncmpServiceCmHandle: cmHandlesToBeCreated) { - if (!rejectedCmHandleIds.contains(ncmpServiceCmHandle.getCmHandleId())) { - yangModelCmHandlesToRegister.add(getYangModelCmHandle(dmiPluginRegistration, ncmpServiceCmHandle)); - cmHandleRegistrationResponses.add( - CmHandleRegistrationResponse.createSuccessResponse(ncmpServiceCmHandle.getCmHandleId())); - succeededCmHandleIds.add(ncmpServiceCmHandle.getCmHandleId()); - } - } - lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister); - dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses); - return succeededCmHandleIds; - } - - private YangModelCmHandle getYangModelCmHandle(final DmiPluginRegistration dmiPluginRegistration, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - return YangModelCmHandle.toYangModelCmHandle( - dmiPluginRegistration.getDmiPlugin(), - dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), - ncmpServiceCmHandle, - ncmpServiceCmHandle.getModuleSetTag(), - ncmpServiceCmHandle.getAlternateId(), - ncmpServiceCmHandle.getDataProducerIdentifier()); - } - private void processTrustLevels(final Collection<NcmpServiceCmHandle> cmHandlesToBeCreated, final Collection<String> succeededCmHandleIds) { final Map<String, TrustLevel> initialTrustLevelPerCmHandleId = new HashMap<>(cmHandlesToBeCreated.size()); @@ -489,9 +274,9 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private static void updateYangModelCmHandleForUpgrade(final YangModelCmHandle yangModelCmHandle, final String upgradedModuleSetTag) { final String lockReasonWithModuleSetTag = String.format(ModuleOperationsUtils.MODULE_SET_TAG_MESSAGE_FORMAT, - upgradedModuleSetTag); + upgradedModuleSetTag); yangModelCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.READY) - .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build()); + .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build()); } private CmHandleRegistrationResponse deleteCmHandleAndGetCmHandleRegistrationResponse(final String cmHandleId) { @@ -569,4 +354,48 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } } + private Collection<String> checkAlternateIds( + final List<NcmpServiceCmHandle> cmHandlesToBeCreated, + final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses) { + final Collection<String> rejectedCmHandleIds = alternateIdChecker + .getIdsOfCmHandlesWithRejectedAlternateId(cmHandlesToBeCreated, AlternateIdChecker.Operation.CREATE); + cmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponses( + rejectedCmHandleIds, ALTERNATE_ID_ALREADY_ASSOCIATED)); + return rejectedCmHandleIds; + } + + private List<String> persistCmHandlesWithState(final DmiPluginRegistration dmiPluginRegistration, + final DmiPluginRegistrationResponse dmiPluginRegistrationResponse, + final List<NcmpServiceCmHandle> cmHandlesToBeCreated, + final Collection<String> rejectedCmHandleIds) { + final List<String> succeededCmHandleIds = new ArrayList<>(cmHandlesToBeCreated.size()); + final List<YangModelCmHandle> yangModelCmHandlesToRegister = new ArrayList<>(cmHandlesToBeCreated.size()); + final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = + new ArrayList<>(cmHandlesToBeCreated.size()); + for (final NcmpServiceCmHandle ncmpServiceCmHandle: cmHandlesToBeCreated) { + if (!rejectedCmHandleIds.contains(ncmpServiceCmHandle.getCmHandleId())) { + yangModelCmHandlesToRegister.add(getYangModelCmHandle(dmiPluginRegistration, ncmpServiceCmHandle)); + cmHandleRegistrationResponses.add( + CmHandleRegistrationResponse.createSuccessResponse(ncmpServiceCmHandle.getCmHandleId())); + succeededCmHandleIds.add(ncmpServiceCmHandle.getCmHandleId()); + } + } + lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister); + dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses); + return succeededCmHandleIds; + } + + private YangModelCmHandle getYangModelCmHandle(final DmiPluginRegistration dmiPluginRegistration, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + return YangModelCmHandle.toYangModelCmHandle( + dmiPluginRegistration.getDmiPlugin(), + dmiPluginRegistration.getDmiDataPlugin(), + dmiPluginRegistration.getDmiModelPlugin(), + ncmpServiceCmHandle, + ncmpServiceCmHandle.getModuleSetTag(), + ncmpServiceCmHandle.getAlternateId(), + ncmpServiceCmHandle.getDataProducerIdentifier()); + } + + } 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/impl/inventory/CmHandleRegistrationServicePropertyHandler.java index f86191002..164d91725 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/impl/inventory/CmHandleRegistrationServicePropertyHandler.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,16 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl; +package org.onap.cps.ncmp.impl.inventory; import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID; -import static org.onap.cps.ncmp.api.impl.NetworkCmProxyDataServicePropertyHandler.PropertyType.DMI_PROPERTY; -import static org.onap.cps.ncmp.api.impl.NetworkCmProxyDataServicePropertyHandler.PropertyType.PUBLIC_PROPERTY; -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.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.DMI_PROPERTY; +import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.PUBLIC_PROPERTY; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; import com.google.common.collect.ImmutableMap; import java.time.OffsetDateTime; @@ -45,16 +45,16 @@ import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; +import org.onap.cps.utils.ContentType; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -64,7 +64,7 @@ import org.springframework.util.StringUtils; @RequiredArgsConstructor //Accepting the security hotspot as the string checked is generated from inside code and not user input. @SuppressWarnings("squid:S5852") -public class NetworkCmProxyDataServicePropertyHandler { +public class CmHandleRegistrationServicePropertyHandler { private final InventoryPersistence inventoryPersistence; private final CpsDataService cpsDataService; @@ -137,6 +137,9 @@ public class NetworkCmProxyDataServicePropertyHandler { log.warn("Unable to update dataProducerIdentifier for cmHandle {}. " + "Value for dataProducerIdentifier has been set previously.", ncmpServiceCmHandle.getCmHandleId()); + } else { + log.debug("dataProducerIdentifier for cmHandle {} is already set to {}.", + ncmpServiceCmHandle.getCmHandleId(), newDataProducerIdentifier); } } else { setAndUpdateCmHandleField( @@ -222,7 +225,7 @@ public class NetworkCmProxyDataServicePropertyHandler { cmHandleData.put(fieldName, newFieldValue); dmiRegistryData.put("cm-handles", cmHandleData); cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - jsonObjectMapper.asJsonString(dmiRegistryData), OffsetDateTime.now()); + jsonObjectMapper.asJsonString(dmiRegistryData), OffsetDateTime.now(), ContentType.JSON); log.debug("Updating {} for cmHandle {} with value : {})", fieldName, cmHandleIdToUpdate, newFieldValue); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CompositeStateUtils.java index 35ad54fde..685d20463 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CompositeStateUtils.java @@ -18,11 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; /** * It will have all the utility method responsible for handling the composite state. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/DataStoreSyncState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/DataStoreSyncState.java index b92d152bf..a260ce9cb 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/DataStoreSyncState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/DataStoreSyncState.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; public enum DataStoreSyncState { SYNCHRONIZED, UNSYNCHRONIZED, NONE_REQUESTED diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java index 184b12570..cb4b04e4a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java @@ -19,13 +19,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; import java.util.Collection; import java.util.List; import java.util.Map; -import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; @@ -130,16 +130,6 @@ public interface InventoryPersistence extends NcmpPersistence { DataNode getCmHandleDataNodeByAlternateId(String alternateId); /** - * Get data node that matches longest alternate id by removing elements (as defined by the separator string) - * from right to left. - * - * @param alternateId alternate ID - * @param separator a string that separates each element from the next. - * @return data node - */ - DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator); - - /** * Get collection of data nodes of given cm handles. * * @param cmHandleIds collection of cmHandle IDs diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index bf54fe5d9..40aefed2a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -20,7 +20,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; @@ -33,13 +33,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; -import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; @@ -59,7 +59,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private final CpsModuleService cpsModuleService; private final CpsAnchorService cpsAnchorService; private final CpsValidator cpsValidator; - private final CmHandleQueries cmHandleQueries; + private final CmHandleQueryService cmHandleQueryService; /** * initialize an inventory persistence object. @@ -72,12 +72,13 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv */ public InventoryPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService, final CpsModuleService cpsModuleService, final CpsValidator cpsValidator, - final CpsAnchorService cpsAnchorService, final CmHandleQueries cmHandleQueries) { + final CpsAnchorService cpsAnchorService, + final CmHandleQueryService cmHandleQueryService) { super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator); this.cpsModuleService = cpsModuleService; this.cpsAnchorService = cpsAnchorService; this.cpsValidator = cpsValidator; - this.cmHandleQueries = cmHandleQueries; + this.cmHandleQueryService = cmHandleQueryService; } @@ -172,7 +173,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv @Override public DataNode getCmHandleDataNodeByAlternateId(final String alternateId) { final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId); - final Collection<DataNode> dataNodes = cmHandleQueries + final Collection<DataNode> dataNodes = cmHandleQueryService .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS); if (dataNodes.isEmpty()) { throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, @@ -182,19 +183,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } @Override - public DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator) { - String bestMatch = alternateId; - while (StringUtils.isNotEmpty(bestMatch)) { - try { - return getCmHandleDataNodeByAlternateId(bestMatch); - } catch (final DataNodeNotFoundException ignored) { - bestMatch = getParentPath(bestMatch, separator); - } - } - throw new NoAlternateIdParentFoundException(alternateId); - } - - @Override public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds) { final Collection<String> xpaths = new ArrayList<>(cmHandleIds.size()); cmHandleIds.forEach(cmHandleId -> xpaths.add(getXPathForCmHandleById(cmHandleId))); @@ -221,9 +209,4 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) { return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } - - private static String getParentPath(final String path, final String separator) { - final int lastSeparatorIndex = path.lastIndexOf(separator); - return lastSeparatorIndex < 0 ? "" : path.substring(0, lastSeparatorIndex); - } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/ncmppersistence/NcmpPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistence.java index d72b5d58f..5271485e3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/ncmppersistence/NcmpPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistence.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.ncmppersistence; +package org.onap.cps.ncmp.impl.inventory; import java.time.OffsetDateTime; import java.util.Collection; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/NcmpPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistenceImpl.java index 6a2d6d810..e44b6ba34 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/NcmpPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistenceImpl.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory; import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; @@ -29,7 +29,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; -import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.DataNode; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java index 06522f80c..e5848c0df 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java @@ -18,13 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api; +package org.onap.cps.ncmp.impl.inventory; import java.util.Collection; -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; -public interface NetworkCmProxyCmHandleQueryService { +public interface ParameterizedCmHandleQueryService { /** * Query and return cm handle ids that match the given query parameters. * Supported query types: @@ -63,7 +63,8 @@ public interface NetworkCmProxyCmHandleQueryService { Collection<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); /** - * Query and return all cm handle objects. + * Get all cm handle objects. + * Note: it is similar to all the queries above but simply no conditions and hence not 'parameterized' * * @return collection of cm handles */ diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java index 8890d14ae..51e403fd6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java @@ -18,16 +18,16 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl; - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; -import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_MODULES; -import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_PROPERTIES; -import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.WITH_CPS_PATH; -import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.WITH_TRUST_LEVEL; -import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCpsPathConditionProperties; -import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateModuleNameConditionProperties; -import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; +package org.onap.cps.ncmp.impl.inventory; + +import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCpsPathConditionProperties; +import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateModuleNameConditionProperties; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HAS_ALL_MODULES; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HAS_ALL_PROPERTIES; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_CPS_PATH; +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_TRUST_LEVEL; +import static org.onap.cps.ncmp.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; @@ -42,15 +42,12 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.PathParsingException; -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.inventory.enums.PropertyType; -import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; +import org.onap.cps.ncmp.impl.inventory.models.PropertyType; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.model.ConditionProperties; import org.onap.cps.spi.model.DataNode; @@ -59,10 +56,10 @@ import org.springframework.stereotype.Service; @Service @Slf4j @RequiredArgsConstructor -public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmHandleQueryService { +public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService { private static final Collection<String> NO_QUERY_TO_EXECUTE = null; - private final CmHandleQueries cmHandleQueries; + private final CmHandleQueryService cmHandleQueryService; private final InventoryPersistence inventoryPersistence; @Override @@ -115,7 +112,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH final String dmiPluginIdentifierValue = dmiPropertyQueryPairs .get(PropertyType.DMI_PLUGIN.getYangContainerName()); - return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); } private Collection<String> queryCmHandlesByPrivateProperties( @@ -128,7 +125,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH if (privatePropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueries.queryCmHandleAdditionalProperties(privatePropertyQueryPairs); + return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs); } private Collection<String> queryCmHandlesByPublicProperties( @@ -141,7 +138,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH if (publicPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs); } private Collection<String> queryCmHandlesByTrustLevel(final CmHandleQueryServiceParameters @@ -154,7 +151,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH if (trustLevelPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueries.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs); } private Collection<String> executeModuleNameQuery( @@ -180,7 +177,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH } try { cpsPathQueryResult = collectCmHandleIdsFromDataNodes( - cmHandleQueries.queryCmHandleAncestorsByCpsPath( + cmHandleQueryService.queryCmHandleAncestorsByCpsPath( cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS)); } catch (final PathParsingException pathParsingException) { throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditions.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditions.java index a59776036..6be5c8ba1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditions.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditions.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils; +package org.onap.cps.ncmp.impl.inventory.models; import java.util.Arrays; import java.util.Collection; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleState.java index 5485ee7e8..9a4b3e2a8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleState.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory.models; public enum CmHandleState { ADVISED, READY, LOCKED, DELETING, DELETED diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditions.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditions.java index 9437cf0ba..fce285b41 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditions.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditions.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils; +package org.onap.cps.ncmp.impl.inventory.models; import java.util.Arrays; import java.util.List; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/LockReasonCategory.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/LockReasonCategory.java index e2b2c6b4a..1003aeca2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/LockReasonCategory.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/LockReasonCategory.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory.models; public enum LockReasonCategory { MODULE_SYNC_FAILED, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/ModelledDmiServiceLeaves.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/ModelledDmiServiceLeaves.java index b8a1ba02d..e9c4957d0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/ModelledDmiServiceLeaves.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/ModelledDmiServiceLeaves.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory; +package org.onap.cps.ncmp.impl.inventory.models; public enum ModelledDmiServiceLeaves { DMI_SERVICE_NAME("dmi-service-name"), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/enums/PropertyType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/PropertyType.java index 08bfe98e5..cc8b094b4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/enums/PropertyType.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/PropertyType.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.enums; +package org.onap.cps.ncmp.impl.inventory.models; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandle.java index 2ca2b2eb0..d96c9c720 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandle.java @@ -19,7 +19,7 @@ */ -package org.onap.cps.ncmp.api.impl.yangmodels; +package org.onap.cps.ncmp.impl.inventory.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -34,9 +34,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.apache.commons.lang3.StringUtils; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; /** * Cm Handle which follows the Yang resource dmi registry model when persisting data to DMI or the DB. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java index 2d5e7a1ab..b8bb64f53 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.executor; +package org.onap.cps.ncmp.impl.inventory.sync; import static java.util.concurrent.TimeUnit.MILLISECONDS; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdog.java index 6f089a57f..45f636784 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdog.java @@ -18,9 +18,9 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync; +package org.onap.cps.ncmp.impl.inventory.sync; -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import com.hazelcast.map.IMap; import java.time.OffsetDateTime; @@ -29,10 +29,9 @@ import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; -import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java index 794ca5b1b..15e4653c9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java @@ -19,9 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync; - -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL; +package org.onap.cps.ncmp.impl.inventory.sync; import com.fasterxml.jackson.databind.JsonNode; import java.time.Duration; @@ -38,14 +36,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState; -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; import org.onap.cps.utils.JsonObjectMapper; @@ -57,7 +55,7 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class ModuleOperationsUtils { - private final CmHandleQueries cmHandleQueries; + private final CmHandleQueryService cmHandleQueryService; private final DmiDataOperations dmiDataOperations; private final JsonObjectMapper jsonObjectMapper; private static final String RETRY_ATTEMPT_KEY = "attempt"; @@ -78,7 +76,8 @@ public class ModuleOperationsUtils { * @return cm handles (data nodes) in ADVISED state (empty list if none found) */ public List<DataNode> getAdvisedCmHandles() { - final List<DataNode> advisedCmHandlesAsDataNodes = cmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED); + final List<DataNode> advisedCmHandlesAsDataNodes = + cmHandleQueryService.queryCmHandlesByState(CmHandleState.ADVISED); log.debug("Total number of fetched advised cm handle(s) is (are) {}", advisedCmHandlesAsDataNodes.size()); return advisedCmHandlesAsDataNodes; } @@ -91,13 +90,13 @@ public class ModuleOperationsUtils { * return empty list if not found */ public List<YangModelCmHandle> getUnsynchronizedReadyCmHandles() { - final List<DataNode> unsynchronizedCmHandles = cmHandleQueries + final List<DataNode> unsynchronizedCmHandles = cmHandleQueryService .queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED); final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(); for (final DataNode unsynchronizedCmHandle : unsynchronizedCmHandles) { final String cmHandleId = unsynchronizedCmHandle.getLeaves().get("id").toString(); - if (cmHandleQueries.cmHandleHasState(cmHandleId, CmHandleState.READY)) { + if (cmHandleQueryService.cmHandleHasState(cmHandleId, CmHandleState.READY)) { yangModelCmHandles.addAll(convertCmHandlesDataNodesToYangModelCmHandles( Collections.singletonList(unsynchronizedCmHandle))); } @@ -113,7 +112,7 @@ public class ModuleOperationsUtils { */ public List<YangModelCmHandle> getCmHandlesThatFailedModelSyncOrUpgrade() { final List<DataNode> lockedCmHandlesAsDataNodeList - = cmHandleQueries.queryCmHandleAncestorsByCpsPath(CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE, + = cmHandleQueryService.queryCmHandleAncestorsByCpsPath(CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); return convertCmHandlesDataNodesToYangModelCmHandles(lockedCmHandlesAsDataNodeList); } @@ -198,16 +197,14 @@ public class ModuleOperationsUtils { } /** - * Get the Resourece Data from Node through DMI Passthrough service. + * Get the Resource Data from Node through DMI Passthrough service. * * @param cmHandleId cm handle id * @return optional string containing the resource data */ public String getResourceData(final String cmHandleId) { - final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getResourceDataFromDmi( - PASSTHROUGH_OPERATIONAL.getDatastoreName(), - cmHandleId, - UUID.randomUUID().toString()); + final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getAllResourceDataFromDmi( + cmHandleId, UUID.randomUUID().toString()); if (resourceDataResponseEntity.getStatusCode().is2xxSuccessful()) { return getFirstResource(resourceDataResponseEntity.getBody()); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncService.java index e257112fc..2c8639a4d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncService.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +19,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync; +package org.onap.cps.ncmp.impl.inventory.sync; -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.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import java.time.OffsetDateTime; import java.util.Collection; @@ -37,16 +38,17 @@ import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.CascadeDeleteAllowed; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.ModuleReference; +import org.onap.cps.utils.ContentType; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; @@ -57,7 +59,7 @@ public class ModuleSyncService { private final DmiModelOperations dmiModelOperations; private final CpsModuleService cpsModuleService; - private final CmHandleQueries cmHandleQueries; + private final CmHandleQueryService cmHandleQueryService; private final CpsDataService cpsDataService; private final CpsAnchorService cpsAnchorService; private final JsonObjectMapper jsonObjectMapper; @@ -134,7 +136,7 @@ public class ModuleSyncService { return null; } final String escapedModuleSetTag = moduleSetTag.replace("'", "''"); - final List<DataNode> dataNodes = cmHandleQueries.queryNcmpRegistryByCpsPath( + final List<DataNode> dataNodes = cmHandleQueryService.queryNcmpRegistryByCpsPath( NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@module-set-tag='" + escapedModuleSetTag + "']", FetchDescendantsOption.DIRECT_CHILDREN_ONLY); return dataNodes.stream().map(YangDataConverter::convertCmHandleToYangModel) @@ -146,6 +148,6 @@ public class ModuleSyncService { final String jsonForUpdate = jsonObjectMapper.asJsonString(Map.of( "cm-handles", Map.of("id", yangModelCmHandle.getId(), "module-set-tag", newModuleSetTag))); cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - jsonForUpdate, OffsetDateTime.now()); + jsonForUpdate, OffsetDateTime.now(), ContentType.JSON); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index 590cb56c4..80c290cb5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync; +package org.onap.cps.ncmp.impl.inventory.sync; import com.hazelcast.map.IMap; import java.util.Collection; @@ -30,12 +30,12 @@ import java.util.concurrent.atomic.AtomicInteger; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler; -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Component; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index 249232d23..b4dde18da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -19,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync; +package org.onap.cps.ncmp.impl.inventory.sync; import com.hazelcast.map.IMap; import java.util.Collection; @@ -31,9 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig; -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.model.DataNode; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java index 62a380ca5..76b33cc8d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.embeddedcache; +package org.onap.cps.ncmp.impl.inventory.sync; import com.hazelcast.config.MapConfig; import com.hazelcast.config.QueueConfig; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurer.java index 1aaee2708..203c5a1a2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurer.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.config; +package org.onap.cps.ncmp.impl.inventory.sync; import java.util.concurrent.ThreadPoolExecutor; import org.springframework.context.annotation.Bean; @@ -53,4 +53,4 @@ public class WatchdogSchedulingConfigurer implements SchedulingConfigurer { taskScheduler.initialize(); return taskScheduler; } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java new file mode 100644 index 000000000..832e576d5 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.utils; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.spi.exceptions.DataNodeNotFoundException; +import org.onap.cps.spi.model.DataNode; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AlternateIdMatcher { + + private final InventoryPersistence inventoryPersistence; + + /** + * Get data node that matches longest alternate id by removing elements (as defined by the separator string) + * from right to left. + * + * @param alternateId alternate ID + * @param separator a string that separates each element from the next. + * @return data node + */ + public DataNode getCmHandleDataNodeByLongestMatchingAlternateId(final String alternateId, final String separator) { + String bestMatch = alternateId; + while (StringUtils.isNotEmpty(bestMatch)) { + try { + return inventoryPersistence.getCmHandleDataNodeByAlternateId(bestMatch); + } catch (final DataNodeNotFoundException ignored) { + bestMatch = getParentPath(bestMatch, separator); + } + } + throw new NoAlternateIdMatchFoundException(alternateId); + } + + private String getParentPath(final String path, final String separator) { + final int lastSeparatorIndex = path.lastIndexOf(separator); + return lastSeparatorIndex < 0 ? "" : path.substring(0, lastSeparatorIndex); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/YangDataConverter.java index 07b92892a..bde13684a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/YangDataConverter.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils; +package org.onap.cps.ncmp.impl.utils; import java.util.Collection; import java.util.LinkedHashMap; @@ -30,10 +30,10 @@ import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.inventory.CompositeState; -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.spi.model.DataNode; @NoArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java index 4cc8cdaa6..554501127 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java @@ -39,7 +39,7 @@ import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.context.event.ApplicationStartedEvent; @Slf4j @RequiredArgsConstructor @@ -61,12 +61,12 @@ abstract class AbstractModelLoader implements ModelLoader { long retryTimeMs; @Override - public void onApplicationEvent(@NonNull final ApplicationReadyEvent applicationReadyEvent) { + public void onApplicationEvent(@NonNull final ApplicationStartedEvent applicationStartedEvent) { try { onboardOrUpgradeModel(); } catch (final NcmpStartUpException ncmpStartUpException) { log.error("Onboarding model for NCMP failed: {} ", ncmpStartUpException.getMessage()); - SpringApplication.exit(applicationReadyEvent.getApplicationContext(), () -> EXIT_CODE_ON_ERROR); + SpringApplication.exit(applicationStartedEvent.getApplicationContext(), () -> EXIT_CODE_ON_ERROR); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java index a0b7bd582..130defb30 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java @@ -20,7 +20,7 @@ package org.onap.cps.ncmp.init; -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; import static org.onap.cps.utils.ContentType.JSON; import java.time.OffsetDateTime; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java index 7c25953f0..76d12f290 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java @@ -20,9 +20,9 @@ package org.onap.cps.ncmp.init; -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.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAnchorService; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java index c61bf1c9b..9832ba3f9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,13 @@ package org.onap.cps.ncmp.init; import lombok.NonNull; -import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; -public interface ModelLoader extends ApplicationListener<ApplicationReadyEvent> { +public interface ModelLoader extends ApplicationListener<ApplicationStartedEvent> { @Override - void onApplicationEvent(@NonNull ApplicationReadyEvent applicationReadyEvent); + void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent); void onboardOrUpgradeModel(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/TopicValidator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/TopicValidator.java new file mode 100644 index 000000000..f9fed8d43 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/TopicValidator.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.utils; + +import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.onap.cps.ncmp.exceptions.InvalidTopicException; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TopicValidator { + + private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" + + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); + + /** + * Validate kafka topic name pattern. + * + * @param topicName name of the topic to be validated + * + * @throws InvalidTopicException if the topic is not valid + */ + public static void validateTopicName(final String topicName) { + if (!TOPIC_NAME_PATTERN.matcher(topicName).matches()) { + throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); + } + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy index 43787640a..9cee2bdba 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy @@ -24,17 +24,25 @@ import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender +import org.onap.cps.ncmp.impl.datajobs.DataJobServiceImpl +import org.onap.cps.ncmp.impl.datajobs.DmiSubJobRequestHandler +import org.onap.cps.ncmp.impl.datajobs.WriteRequestExaminer import org.slf4j.LoggerFactory -import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest -import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest -import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata -import org.onap.cps.ncmp.api.models.datajob.ReadOperation -import org.onap.cps.ncmp.api.models.datajob.WriteOperation +import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata +import org.onap.cps.ncmp.api.datajobs.models.ReadOperation +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation import spock.lang.Specification -class DataJobServiceImplSpec extends Specification{ +class DataJobServiceImplSpec extends Specification { - def objectUnderTest = new DataJobServiceImpl() + def mockWriteRequestExaminer = Mock(WriteRequestExaminer) + def mockDmiSubJobRequestHandler = Mock(DmiSubJobRequestHandler) + + def objectUnderTest = new DataJobServiceImpl(mockDmiSubJobRequestHandler, mockWriteRequestExaminer) + + def myDataJobMetadata = new DataJobMetadata('', '', '') def logger = Spy(ListAppender<ILoggingEvent>) @@ -46,28 +54,27 @@ class DataJobServiceImplSpec extends Specification{ ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class)).detachAndStopAllAppenders() } - def '#operation data job request.'() { - given: 'data job metadata' - def dataJobMetadata = new DataJobMetadata('client-topic', 'application/vnd.3gpp.object-tree-hierarchical+json', 'application/3gpp-json-patch+json') - when: 'read/write data job request is processed' - if (operation == 'read') { - objectUnderTest.readDataJob('some-job-id', dataJobMetadata, new DataJobReadRequest([getWriteOrReadOperationRequest(operation)])) - } else { - objectUnderTest.writeDataJob('some-job-id', dataJobMetadata, new DataJobWriteRequest([getWriteOrReadOperationRequest(operation)])) - } + def 'Read data job request.'() { + when: 'read data job request is processed' + def readOperation = new ReadOperation('', '', '', [], [], '', '', 1) + objectUnderTest.readDataJob('my-job-id', myDataJobMetadata, new DataJobReadRequest([readOperation])) then: 'the data job id is correctly logged' def loggingEvent = logger.list[0] assert loggingEvent.level == Level.INFO - assert loggingEvent.formattedMessage.contains('data job id for ' + operation + ' operation is: some-job-id') - where: 'the following data job operations are used' - operation << ['read', 'write'] + assert loggingEvent.formattedMessage.contains('data job id for read operation is: my-job-id') } - def getWriteOrReadOperationRequest(operation) { - if (operation == 'write') { - return new WriteOperation('some/write/path', 'add', 'some-operation-id', 'some-value') - } - return new ReadOperation('some/read/path', 'read', 'some-operation-id', ['some-attrib-1'], ['some-field-1'], 'some-filter', 'some-scope-type', 1) + def 'Write data-job request.'() { + given: 'data job metadata and write request' + def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('', '', '', null)]) + and: 'a map of producer key and dmi 3gpp write operation' + def dmiWriteOperationsPerProducerKey = [:] + when: 'write data job request is processed' + objectUnderTest.writeDataJob('my-job-id', myDataJobMetadata, dataJobWriteRequest) + then: 'the examiner service is called and a map is returned' + 1 * mockWriteRequestExaminer.splitDmiWriteOperationsFromRequest('my-job-id', dataJobWriteRequest) >> dmiWriteOperationsPerProducerKey + and: 'the dmi request handler is called with the result from the examiner' + 1 * mockDmiSubJobRequestHandler.sendRequestsToDmi('my-job-id', myDataJobMetadata, dmiWriteOperationsPerProducerKey) } def setupLogger() { @@ -76,4 +83,4 @@ class DataJobServiceImplSpec extends Specification{ setupLogger.addAppender(logger) logger.start() } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy new file mode 100644 index 000000000..8dc3d223b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy @@ -0,0 +1,41 @@ +package org.onap.cps.ncmp.api.impl + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata +import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation +import org.onap.cps.ncmp.api.datajobs.models.ProducerKey +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse +import org.onap.cps.ncmp.api.impl.client.DmiRestClient +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.api.impl.operations.OperationType +import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService +import org.onap.cps.ncmp.impl.datajobs.DmiSubJobRequestHandler +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import spock.lang.Specification + +class DmiSubJobRequestHandlerSpec extends Specification { + + def mockDmiRestClient = Mock(DmiRestClient) + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockDmiProperties = Mock(DmiProperties) + def static NO_AUTH = null + def objectUnderTest = new DmiSubJobRequestHandler(mockDmiRestClient, mockDmiProperties, jsonObjectMapper) + + def 'Send a sub-job request to the DMI Plugin.'() { + given: 'a data job id, metadata and a map of producer keys and write operations to create a request' + def dataJobId = 'some-job-id' + def dataJobMetadata = new DataJobMetadata('', '', '') + def dmiWriteOperation = new DmiWriteOperation('', '', '', null, '', [:]) + def dmiWriteOperationsPerProducerKey = [new ProducerKey('', ''): [dmiWriteOperation]] + def response = new ResponseEntity<>(new SubJobWriteResponse('my-sub-job-id', '', ''), HttpStatus.OK) + when: 'sending request to DMI invoked' + objectUnderTest.sendRequestsToDmi(dataJobId, dataJobMetadata, dmiWriteOperationsPerProducerKey) + then: 'the dmi rest client is called' + 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(RequiredDmiService.DATA, _, _, OperationType.CREATE, NO_AUTH) >> response + and: 'the result contains the expected sub-job write responses' + def result = response.body + assert result.subJobId() == 'my-sub-job-id' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy new file mode 100644 index 000000000..781b6204a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl + +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.NetworkCmProxyQueryService +import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class NcmpCachedResourceRequestHandlerSpec extends Specification { + + def cpsDataService = Mock(CpsDataService) + def networkCmProxyQueryService= Mock(NetworkCmProxyQueryService) + + def objectUnderTest = new NcmpCachedResourceRequestHandler(cpsDataService, networkCmProxyQueryService) + + def 'Execute a request with include descendants = #includeDescendants.'() { + when: 'executing a request' + objectUnderTest.executeRequest('ch-1', 'resource', includeDescendants) + then: 'it is delegated to the ncmp query service with the correct option' + 1 * networkCmProxyQueryService.queryResourceDataOperational('ch-1','resource', expectedFetchDescendantsOption) + where: 'the following options are used' + includeDescendants || expectedFetchDescendantsOption + true || INCLUDE_ALL_DESCENDANTS + false || OMIT_DESCENDANTS + } + + def 'Get resource data.'() { + given: 'the data service returns 2 nodes for the given resource address' + def cmResourceAddress = new CmResourceAddress('datastore','ch-1','resource') + def dataNode1 = new DataNode(xpath:'p1') + def dataNode2 = new DataNode(xpath:'p2') + cpsDataService.getDataNodes('datastore','ch-1','resource',OMIT_DESCENDANTS) >> [dataNode1, dataNode2] + when: 'getting the resource data' + def result = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', 'topic', 'request id', false, 'authorization') + then: 'the result is a "Mono" holding just the first data node' + assert result instanceof Mono + assert result.block() == dataNode1 + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy new file mode 100644 index 000000000..9a845c0ba --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy @@ -0,0 +1,133 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 + +import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException +import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations +import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.ncmp.api.models.DataOperationDefinition +import org.onap.cps.ncmp.api.models.DataOperationRequest +import org.onap.cps.ncmp.exceptions.OperationNotSupportedException +import org.onap.cps.ncmp.exceptions.PayloadTooLargeException +import org.springframework.http.ResponseEntity +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT + +class NcmpDatastoreRequestHandlerSpec extends Specification { + + def dmiDataOperations = Mock(DmiDataOperations) + + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + + def NO_TOPIC = null + def NO_AUTH_HEADER = null + + def 'Attempt to execute async get request with #scenario.'() { + given: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + and: 'a CM resource address' + def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1') + and: 'the (mocked) service when called with the correct parameters (with or without topic) returns a response from dmi' + def dmiResponse = Mono.justOrEmpty(new ResponseEntity('dmi response',I_AM_A_TEAPOT)) + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', NO_TOPIC, _, NO_AUTH_HEADER) >> dmiResponse + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', topic, _, NO_AUTH_HEADER) >> dmiResponse + when: 'get request is executed with topic = #topic' + def response = objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER) + then: 'a successful result with/without request id is returned' + if (expectSynchronousResponse) { + assert response == 'dmi response' + } else { // expect request id in a map + assert response.keySet()[0] == 'requestId' + } + where: 'the following parameters are used' + scenario | notificationFeatureEnabled | topic || expectSynchronousResponse + 'feature on, valid topic' | true | 'valid' || false + 'feature on, no topic' | true | null || true + 'feature off, valid topic' | false | 'valid' || true + 'feature off, no topic' | false | null || true + } + + def 'Attempt to execute async data operation request with feature #scenario.'() { + given: 'a extended request handler that supports bulk requests' + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + and: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + when: 'data operation request is executed' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch']) + def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the task is executed in an async fashion or not' + expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) + and: + result.keySet()[0] == expectedKeyInMap + where: 'the following parameters are used' + scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap + 'on' | true || 1 || 'requestId' + 'off' | false || 0 || 'status' + } + + def 'Attempt to execute async data operation request with error #scenario'() { + given: 'a data operation definition with datastore: #datastore' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) + when: 'data operation request is executed' + def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) + objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) + then: 'the correct error is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains(expectedErrorMessage) + where: 'the following datastore names are used' + scenario | datastore || expectedErrorMessage + 'unsupported datastore' | 'ncmp-datastore:operational' || 'not supported' + 'invalid datastore' | 'invalid' || 'invalid datastore name' + } + + def 'Attempt to execute async data operation request with #scenario operation: #operation.'() { + given: 'a data operation definition with operation: #operation' + def dataOperationDefinition = new DataOperationDefinition(operation: operation, datastore: 'ncmp-datastore:passthrough-running') + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the expected type of exception is thrown' + thrown(expectedException) + where: 'the following operations are used' + scenario | operation || expectedException + 'invalid' | 'invalid' || InvalidOperationException + 'unsupported' | 'create' || OperationNotSupportedException + 'unsupported' | 'update' || OperationNotSupportedException + 'unsupported' | 'patch' || OperationNotSupportedException + 'unsupported' | 'delete' || OperationNotSupportedException + } + + def 'Attempt to execute async data operation request with too many cm handles.'() { + given: 'a data operation definition with too many cm handles' + def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1 + def cmHandleIds = new String[tooMany] + def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds) + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'a payload too large exception is thrown' + def exceptionThrown = thrown(PayloadTooLargeException) + and: 'the error message contains the offending number of cm handles' + assert exceptionThrown.message == "Operation 'abc' affects too many (${tooMany}) cm handles" + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy deleted file mode 100644 index 4d0af6f49..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ /dev/null @@ -1,411 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -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.operations.DatastoreType.OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE - -import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.models.DataOperationDefinition -import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.ConditionApiProperties -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.spi.exceptions.CpsException -import org.onap.cps.spi.model.ConditionProperties -import java.util.stream.Collectors -import org.onap.cps.utils.JsonObjectMapper -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import spock.lang.Specification - -class NetworkCmProxyDataServiceImplSpec extends Specification { - - def mockCpsDataService = Mock(CpsDataService) - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockDmiDataOperations = Mock(DmiDataOperations) - def nullNetworkCmProxyDataServicePropertyHandler = null - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) - def mockDmiPluginRegistration = Mock(DmiPluginRegistration) - def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService) - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) - def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>) - def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>) - def mockTrustLevelManager = Mock(TrustLevelManager) - def mockAlternateIdChecker = Mock(AlternateIdChecker) - - def NO_TOPIC = null - def NO_REQUEST_ID = null - def NO_AUTH_HEADER = null - def OPTIONS_PARAM = '(a=1,b=2)' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id') - - def objectUnderTest = new NetworkCmProxyDataServiceImpl( - spiedJsonObjectMapper, - mockDmiDataOperations, - nullNetworkCmProxyDataServicePropertyHandler, - mockInventoryPersistence, - mockCmHandleQueries, - mockCpsCmHandlerQueryService, - mockLcmEventsCmHandleStateHandler, - mockCpsDataService, - stubModuleSyncStartedOnCmHandles, - stubTrustLevelPerDmiPlugin, - mockTrustLevelManager, - mockAlternateIdChecker) - - def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" - - def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])] - - def 'Write resource data for pass-through running from DMI using POST.'() { - given: 'cpsDataService returns valid datanode' - mockDataNode() - when: 'write resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', CREATE, - '{some-json}', 'application/json', null) - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - CREATE, '{some-json}', 'application/json', null) - >> { new ResponseEntity<>(HttpStatus.CREATED) } - } - - def 'Get resource data for from DMI.'() { - given: 'cpsDataService returns valid data node' - mockDataNode() - and: 'some cm resource address' - def cmResourceAddress = new CmResourceAddress('some datastore','some CM Handle', 'some resource Id') - and: 'get resource data from DMI is called' - mockDmiDataOperations.getResourceDataFromDmi(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >> - new ResponseEntity<>('dmi-response', HttpStatus.OK) - when: 'get resource data operational for the given cm resource address is called' - def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) - then: 'DMI returns a json response' - assert response == 'dmi-response' - } - - def 'Get resource data for operational (cached) data.'() { - given: 'CPS Data service returns some object(s)' - mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object'] - and: 'a cm resource address for the same datastore, cm handle and resource id' - def cmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId') - when: 'get resource data is called' - def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, FetchDescendantsOption.OMIT_DESCENDANTS) - then: 'get resource data returns teh first object from the data service' - assert response == 'First Object' - } - - def 'Execute (async) data operation for #datastoreName from DMI.'() { - given: 'cpsDataService returns valid data node' - def dataOperationRequest = getDataOperationRequest(datastoreName) - when: 'request resource data for data operation is called' - objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - then: 'request resource data for data operation returns expected response' - 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - where: 'the following data stores are used' - datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName] - } - - def 'Getting Yang Resources.'() { - when: 'yang resources is called' - objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') - then: 'CPS module services is invoked for the correct dataspace and cm handle' - 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') - } - - def 'Get a cm handle.'() { - given: 'the system returns a yang modelled cm handle' - def dmiServiceName = 'some service name' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] - def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] - def moduleSetTag = 'some-module-set-tag' - def alternateId = 'some-alternate-id' - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, - dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, - moduleSetTag: moduleSetTag, alternateId: alternateId) - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle details for a given cm handle id from ncmp service' - def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') - then: 'the result is a ncmpServiceCmHandle' - result.class == NcmpServiceCmHandle.class - and: 'the cm handle contains the cm handle id' - result.cmHandleId == 'some-cm-handle' - and: 'the cm handle contains the alternate id' - result.alternateId == 'some-alternate-id' - and: 'the cm handle contains the module-set-tag' - result.moduleSetTag == 'some-module-set-tag' - and: 'the cm handle contains the DMI Properties' - result.dmiProperties ==[ Book:'Romance Novel' ] - and: 'the cm handle contains the public Properties' - result.publicProperties == [ "Public Book":'Public Romance Novel' ] - and: 'the cm handle contains the cm handle composite state' - result.compositeState == compositeState - } - - def 'Get cm handle public properties'() { - given: 'a yang modelled cm handle' - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle public properties for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') - then: 'the result returns the correct data' - result == [ 'public prop' : 'some public prop' ] - } - - def 'Execute cm handle id search for inventory'() { - given: 'a ConditionApiProperties object' - def conditionProperties = new ConditionProperties() - conditionProperties.conditionName = 'hasAllProperties' - conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] - def conditionServiceProps = new CmHandleQueryServiceParameters() - conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties> - and: 'the system returns an set of cmHandle ids' - mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] - when: 'getting cm handle id set for a given dmi property' - def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps) - then: 'the result returns the correct 2 elements' - assert result.size() == 2 - assert result.contains('cmHandle1') - assert result.contains('cmHandle2') - } - - def 'Get cm handle composite state'() { - given: 'a yang modelled cm handle' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle composite state for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') - then: 'the result returns the correct data' - result == compositeState - } - - def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() { - given: 'cpsDataService returns valid datanode' - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode - when: 'get resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', UPDATE, - '{some-json}', 'application/json', NO_AUTH_HEADER) - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - UPDATE, '{some-json}', 'application/json', NO_AUTH_HEADER) - >> { new ResponseEntity<>(HttpStatus.OK) } - } - - def 'Verify modules and create anchor params.'() { - given: 'dmi plugin registration return created cm handles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1', - dmiDataPlugin: 'service2') - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle] - and: 'no rejected cm handles because of alternate ids' - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - when: 'parse and create cm handle in dmi registration then sync module' - mockDmiPluginRegistration.createdCmHandles = ['test-cm-handle-id'] - objectUnderTest.processCreatedCmHandles(mockDmiPluginRegistration, new DmiPluginRegistrationResponse()) - then: 'system persists the cm handle state' - 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { - args -> { - def cmHandleStatePerCmHandle = (args[0] as Collection) - cmHandleStatePerCmHandle.each { - assert it.id == 'test-cm-handle-id' - } - } - } - } - - def 'Execute cm handle id search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockCpsCmHandlerQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> ['cm-handle-id-1'] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result == ['cm-handle-id-1'] - } - - def 'Getting module definitions by module'() { - when: 'get module definitions is performed with module name' - objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - then: 'ncmp inventory persistence service is invoked once with correct parameters' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - } - - def 'Getting module definitions by cm handle id'() { - when: 'get module definitions is performed with cm handle id' - objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') - then: 'ncmp inventory persistence service is invoked once with correct parameter' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') - } - - def 'Execute cm handle search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockCpsCmHandlerQueryService.queryCmHandles( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set - } - - def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { - given: 'an existing cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, - dataStores: CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(initialDataSyncState) - .build()).build()) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) - then: 'the data sync enabled flag is set to #dataSyncEnabled' - compositeState.dataSyncEnabled == dataSyncEnabledFlag - and: 'the data store sync state is set to #expectedDataStoreSyncState' - compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState - and: 'the cps data service to delete data nodes is invoked the expected number of times' - deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) - and: 'the inventory persistence service to update node leaves is called with the correct values' - saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) - where: 'the following data sync enabled flag is used' - scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations - 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 - 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 - 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 - 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 - } - - def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { - given: 'a cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to true' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) - then: 'the expected exception is thrown' - thrown(CpsException) - and: 'the inventory persistence service to update node leaves is not invoked' - 0 * mockInventoryPersistence.saveCmHandleState(_, _) - } - - def 'Get all cm handle IDs by DMI plugin identifier.' () { - given: 'cm handle queries service returns cm handles' - 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] - when: 'cm handle Ids are requested with dmi plugin identifier' - def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') - then: 'the result size is correct' - assert result.size() == 2 - and: 'the result returns the correct details' - assert result.containsAll('cm-handle-1','cm-handle-2') - } - - def dataStores() { - CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) - .lastSyncTime('some-timestamp').build()).build() - } - - def mockDataNode() { - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode - } - - def getDataOperationRequest(datastore) { - def dataOperationRequest = new DataOperationRequest() - def dataOperationDefinitions = new ArrayList() - dataOperationDefinitions.add(getDataOperationDefinition(datastore)) - dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions) - return dataOperationRequest - } - - def getDataOperationDefinition(datastore) { - def dataOperationDefinition = new DataOperationDefinition() - dataOperationDefinition.setOperation("read") - dataOperationDefinition.setOperationId("operational-12") - dataOperationDefinition.setDatastore(datastore) - def targetIds = new ArrayList() - targetIds.add("some-cm-handle") - dataOperationDefinition.setCmHandleIds(targetIds) - return dataOperationDefinition - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy new file mode 100644 index 000000000..f79e0ee2e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy @@ -0,0 +1,108 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl + +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations +import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.ncmp.api.models.DataOperationRequest +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE + +class NetworkCmProxyFacadeSpec extends Specification { + + def mockDmiDataOperations = Mock(DmiDataOperations) + def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) + def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) + + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) + + def NO_TOPIC = null + + def 'Execute Data Operation for CM Handles (delegation).'() { + given: 'a data operation request' + def dataOperationRequest = Mock(DataOperationRequest) + and: ' a response from the (mocked) pass-through request handler for the given parameters' + def responseFromHandler = [attr:'value'] + mockNcmpPassthroughResourceRequestHandler.executeAsynchronousRequest('topic', dataOperationRequest, 'authorization') >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.executeDataOperationForCmHandles('topic', dataOperationRequest, 'authorization') == responseFromHandler + } + + def 'Query Resource Data for cm handle (delegation).'() { + given: 'a response from the (mocked) cached data handler for the given parameters' + def responseFromHandler = [Mock(DataNode)] + mockNcmpCachedResourceRequestHandler.executeRequest('ch-1', 'some cps path', true) >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.queryResourceDataForCmHandle('ch-1','some cps path',true) == responseFromHandler + } + + def 'Choosing Data Request Handler.'() { + expect: '(a mock of) #expectedHandler' + assert objectUnderTest.getNcmpDatastoreRequestHandler(datastore.datastoreName).class.name.startsWith(expectedHandler.name) + where: + datastore || expectedHandler + OPERATIONAL || NcmpCachedResourceRequestHandler.class + PASSTHROUGH_RUNNING || NcmpPassthroughResourceRequestHandler.class + PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler.class + } + + def 'Write resource data for pass-through running from DMI using POST (delegation).'() { + when: 'write resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', CREATE, + '{some-json}', 'application/json', null) + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json', null) + } + + def 'Get resource data from DMI (delegation).'() { + given: 'a cm resource address for datastore operational' + def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') + and: 'get resource data from DMI is called' + mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> + Mono.just('dmi response') + when: 'get resource data operational for the given cm resource address is called' + def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization').block() + then: 'DMI returns a json response' + assert response == 'dmi response' + } + + def 'Update resource data for pass-through running from dmi (delegation).'() { + when: 'get resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', UPDATE, + '{some-json}', 'application/json', 'authorization') + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', UPDATE, '{some-json}', 'application/json', 'authorization') + } + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy new file mode 100644 index 000000000..84eb78b75 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy @@ -0,0 +1,63 @@ + +package org.onap.cps.ncmp.impl.datajobs + +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class WriteRequestExaminerSpec extends Specification { + + def mockAlternateIdMatcher = Mock(AlternateIdMatcher) + def objectUnderTest = new WriteRequestExaminer(mockAlternateIdMatcher) + + def setup() { + def ch1 = new DataNode(leaves: [id: 'ch1', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1']) + def ch2 = new DataNode(leaves: [id: 'ch2', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1']) + def ch3 = new DataNode(leaves: [id: 'ch3', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p2']) + def ch4 = new DataNode(leaves: [id: 'ch4', 'dmi-service-name': 'dmiB', 'data-producer-identifier': 'p1']) + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn1', '/') >> ch1 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn2', '/') >> ch2 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn3', '/') >> ch3 + mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn4', '/') >> ch4 + } + + def 'Create a map of dmi write requests per producer key with #scenario.'() { + given: 'a write request with some write operations' + def writeOperations = writeOperationFdns.collect { + new WriteOperation(it, '', '', null) + } + and: 'operations are wrapped in a write request' + def dataJobWriteRequest = new DataJobWriteRequest(writeOperations) + when: 'the DMI write operations are split from the request' + def dmiWriteOperationsPerProducerKey = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest) + then: 'we get the expected number of keys and values.' + def producerKeysAsStrings = dmiWriteOperationsPerProducerKey.keySet().collect { + it.toString() + } + assert producerKeysAsStrings.size() == expectedKeys.size() + assert expectedKeys.containsAll(producerKeysAsStrings) + where: + scenario | writeOperationFdns || expectedKeys + 'one fdn' | ['fdn1'] || ['dmiA#p1'] + 'a duplicated target' | ['fdn1','fdn1'] || ['dmiA#p1'] + 'two different targets' | ['fdn1','fdn2'] || ['dmiA#p1'] + 'two different targets and different producer keys' | ['fdn1','fdn3'] || ['dmiA#p1', 'dmiA#p2'] + 'two different targets and different DMI services' | ['fdn1','fdn4'] || ['dmiA#p1', 'dmiB#p1'] + 'many targets with different dmi service names and producer keys' | ['fdn1', 'fdn2', 'fdn3', 'fdn4'] || ['dmiA#p1', 'dmiA#p2', 'dmiB#p1'] + } + + def 'Validate the ordering of the created sub jobs.'() { + given: 'a few write operations for the same producer' + def writeOperations = (1..3).collect { + new WriteOperation('fdn1', '', it.toString(), null) + } + and: 'operation is wrapped in a write request' + def dataJobWriteRequest = new DataJobWriteRequest(writeOperations) + when: 'the DMI write operations are split from the request' + def dmiWriteOperations = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest).values().iterator().next() + then: 'we get the operation ids in the expected order.' + assert dmiWriteOperations.operationId == ['1', '2', '3'] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy index 369b496ca..b095f904a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy @@ -20,6 +20,8 @@ package org.onap.cps.ncmp.api.impl.async +import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent + import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import io.cloudevents.kafka.CloudEventDeserializer @@ -42,8 +44,6 @@ import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers import java.time.Duration -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent - @SpringBootTest(classes = [EventsPublisher, DataOperationEventConsumer, RecordFilterStrategies, JsonObjectMapper, ObjectMapper]) @Testcontainers @DirtiesContext 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 c8e34b1a5..bb73c6879 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,89 +24,125 @@ package org.onap.cps.ncmp.api.impl.client 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.config.DmiProperties +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException +import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException import org.onap.cps.ncmp.utils.TestUtils -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.HttpEntity +import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.test.context.ContextConfiguration -import org.springframework.web.client.HttpServerErrorException -import org.springframework.web.client.RestTemplate +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.WebClientRequestException +import org.springframework.web.reactive.function.client.WebClientResponseException +import reactor.core.publisher.Mono import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH +import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL -@SpringBootTest -@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper]) class DmiRestClientSpec extends Specification { static final NO_AUTH_HEADER = null - static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ=' + static final BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ=' static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token' - @SpringBean - RestTemplate mockRestTemplate = Mock(RestTemplate) + def mockDataServicesWebClient = Mock(WebClient) + def mockModelServicesWebClient = Mock(WebClient) + def mockHealthChecksWebClient = Mock(WebClient) - @Autowired - NcmpConfiguration.DmiProperties dmiProperties + def mockRequestBody = Mock(WebClient.RequestBodyUriSpec) + def mockResponse = Mock(WebClient.ResponseSpec) - @Autowired - DmiRestClient objectUnderTest + def mockDmiProperties = Mock(DmiProperties) - @Autowired - ObjectMapper objectMapper + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def responseFromRestTemplate = Mock(ResponseEntity) + DmiRestClient objectUnderTest = new DmiRestClient(mockDmiProperties, jsonObjectMapper, mockDataServicesWebClient, mockModelServicesWebClient, mockHealthChecksWebClient) - def 'DMI POST operation with JSON.'() { - 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('my url', 'some json', READ, null) + def setup() { + mockRequestBody.uri(_) >> mockRequestBody + mockRequestBody.headers(_) >> mockRequestBody + mockRequestBody.body(_) >> mockRequestBody + mockRequestBody.retrieve() >> mockResponse + } + + def 'DMI POST Operation with JSON for DMI Data Service '() { + given: 'the Data web client returns a valid response entity for the expected parameters' + mockDataServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT)) + when: 'POST operation is invoked fro Data Service' + def response = objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) then: 'the output of the method is equal to the output from the test template' - result == responseFromRestTemplate + assert response.statusCode == HttpStatus.I_AM_A_TEAPOT + assert response.body == 'from Data service' } - def 'Failing DMI POST operation.'() { - given: 'the rest template returns a valid response entity' - def serverResponse = 'server response'.getBytes() - def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null) - mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException } - when: 'POST operation is invoked' - def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null) - then: 'a Http Client Exception is thrown' - def thrown = thrown(HttpClientRequestException) + def 'DMI POST Operation with JSON for DMI Model Service '() { + given: 'the Model web client returns a valid response entity for the expected parameters' + mockModelServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT)) + when: 'POST operation is invoked for Model Service' + def response = objectUnderTest.synchronousPostOperationWithJsonData(MODEL, '/my/url', 'some json', READ, NO_AUTH_HEADER) + then: 'the output of the method is equal to the output from the test template' + assert response.statusCode == HttpStatus.I_AM_A_TEAPOT + assert response.body == 'from Model service' + } + + def 'Failing DMI POST operation due to invalid dmi resource url.'() { + when: 'POST operation is invoked with invalid dmi resource url' + objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/invalid dmi url', null, null, NO_AUTH_HEADER) + then: 'invalid dmi resource url exception is thrown' + def thrown = thrown(InvalidDmiResourceUrlException) and: 'the exception has the relevant details from the error response' - assert thrown.httpStatus == 403 - assert thrown.message == "Unable to ${operation} resource data." - assert thrown.details == 'server response' - where: 'the following operation is executed' + thrown.httpStatus == 400 + thrown.message == 'Invalid dmi resource url: /invalid dmi url' + where: 'the following operations are executed' operation << [CREATE, READ, PATCH] } + def 'Dmi service sends client error response when #scenario'() { + given: 'the web client unable to return response entity but error' + mockDataServicesWebClient.post() >> mockRequestBody + mockResponse.toEntity(Object.class) >> Mono.error(exceptionType) + when: 'POST operation is invoked' + objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER) + then: 'a http client exception is thrown' + def thrown = thrown(DmiClientRequestException) + and: 'the exception has the relevant details from the error response' + assert thrown.ncmpResponseStatus == expectedNcmpResponseStatusCode + assert thrown.httpStatusCode == httpStatusCode + where: 'the following errors occur' + scenario | httpStatusCode | exceptionType || expectedNcmpResponseStatusCode + 'dmi service unavailable' | 503 | new WebClientRequestException(new RuntimeException('some-error'), null, null, new HttpHeaders()) || DMI_SERVICE_NOT_RESPONDING + 'dmi request timeout' | 408 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || DMI_SERVICE_NOT_RESPONDING + 'dmi server error' | 500 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || UNABLE_TO_READ_RESOURCE_DATA + 'unknown error' | 500 | new Throwable('message') || UNKNOWN_ERROR + } + 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) + def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class) ((ObjectNode) jsonNode).put('status', 'my status') - mockRestTemplate.getForObject(*_) >> {jsonNode} + mockHealthChecksWebClient.get() >> mockRequestBody + mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode) when: 'get trust level of the dmi plugin' - def result = objectUnderTest.getDmiHealthStatus('some url') - then: 'the status value from the json is return' + def result = objectUnderTest.getDmiHealthStatus('some/url') + then: 'the status value from the json is returned' assert result == 'my status' } def 'Failing to get dmi plugin health status #scenario'() { given: 'rest template with #scenario' - mockRestTemplate.getForObject(*_) >> healthStatusResponse + mockHealthChecksWebClient.get() >> mockRequestBody + mockResponse.bodyToMono(_) >> healthStatusResponse when: 'attempt to get health status of the dmi plugin' def result = objectUnderTest.getDmiHealthStatus('some url') then: 'result will be empty' @@ -114,15 +150,18 @@ class DmiRestClientSpec extends Specification { where: 'the following responses are used' scenario | healthStatusResponse 'null' | null - 'exception' | {throw new Exception()} + 'exception' | { throw new Exception() } } def 'DMI auth header #scenario'() { when: 'Specific dmi properties are provided' - dmiProperties.dmiBasicAuthEnabled = authEnabled + mockDmiProperties.dmiBasicAuthEnabled >> authEnabled + mockDmiProperties.authUsername >> 'some user' + mockDmiProperties.authPassword >> 'some password' then: 'http headers to conditionally have Authorization header' - def authHeaderValues = objectUnderTest.configureHttpHeaders(new HttpHeaders(), ncmpAuthHeader).getOrEmpty('Authorization') - def outputAuthHeader = (authHeaderValues == null ? null : authHeaderValues[0]) + def httpHeaders = new HttpHeaders() + objectUnderTest.configureHttpHeaders(httpHeaders, ncmpAuthHeader) + def outputAuthHeader = (httpHeaders.Authorization == null ? null : httpHeaders.Authorization[0]) assert outputAuthHeader == expectedAuthHeader where: 'the following configurations are used' scenario | authEnabled | ncmpAuthHeader || expectedAuthHeader @@ -132,5 +171,4 @@ class DmiRestClientSpec extends Specification { 'DMI basic auth disabled, with NCMP bearer token' | false | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER 'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy new file mode 100644 index 000000000..c763c522c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import spock.lang.Specification + +class DmiPropertiesSpec extends Specification { + + def objectUnderTest = new DmiProperties() + + def 'Geting dmi base path.'() { + given: 'base path of #dmiBasePath' + objectUnderTest.dmiBasePath = dmiBasePath + expect: 'Preceding and trailing slash wil be removed' + assert objectUnderTest.getDmiBasePath() == 'test' + where: 'the following dmi base paths are used' + dmiBasePath << [ 'test' , '/test', 'test/', '/test/' ] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy new file mode 100644 index 000000000..05ecaa11b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.web.reactive.function.client.WebClient +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [HttpClientConfiguration]) +@TestPropertySource(properties = ['ncmp.dmi.httpclient.data-services.connectionTimeoutInSeconds=1', 'ncmp.dmi.httpclient.model-services.maximumInMemorySizeInMegabytes=1']) +@EnableConfigurationProperties +class DmiWebClientConfigurationSpec extends Specification { + + def httpClientConfiguration = Spy(HttpClientConfiguration.class) + + def objectUnderTest = new DmiWebClientConfiguration(httpClientConfiguration) + + def 'Web Client Configuration construction.'() { + expect: 'the system can create an instance' + new DmiWebClientConfiguration(httpClientConfiguration) != null + } + + def 'Creating a web client instance data service.'() { + given: 'Web client configuration is invoked' + def dataServicesWebClient = objectUnderTest.dataServicesWebClient() + expect: 'the system can create an instance for data service' + assert dataServicesWebClient != null + assert dataServicesWebClient instanceof WebClient + } + + def 'Creating a web client instance model service.'() { + given: 'Web client configuration invoked' + def modelServicesWebClient = objectUnderTest.modelServicesWebClient() + expect: 'the system can create an instance for model service' + assert modelServicesWebClient != null + assert modelServicesWebClient instanceof WebClient + } + + def 'Creating a web client instance health service.'() { + given: 'Web client configuration invoked' + def healthChecksWebClient = objectUnderTest.healthChecksWebClient() + expect: 'the system can create an instance for health service' + assert healthChecksWebClient != null + assert healthChecksWebClient instanceof WebClient + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy index 2c76b5bb4..228f41277 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. + * Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,32 +17,58 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ + package org.onap.cps.ncmp.api.impl.config -import java.time.Duration import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource -import org.springframework.test.context.support.AnnotationConfigContextLoader import spock.lang.Specification @SpringBootTest @ContextConfiguration(classes = [HttpClientConfiguration]) @EnableConfigurationProperties(HttpClientConfiguration.class) -@TestPropertySource(properties = ["ncmp.dmi.httpclient.connectionTimeoutInSeconds=1", "ncmp.dmi.httpclient.maximumConnectionsTotal=200"]) +@TestPropertySource(properties = ["ncmp.dmi.httpclient.data-services.readTimeoutInSeconds=789", "ncmp.dmi.httpclient.model-services.maximumConnectionsTotal=111"]) class HttpClientConfigurationSpec extends Specification { @Autowired private HttpClientConfiguration httpClientConfiguration - def 'Test HttpClientConfiguration properties with custom and default values'() { - expect: 'custom property values' - assert httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1) - assert httpClientConfiguration.getMaximumConnectionsTotal() == 200 - and: 'default property values' - assert httpClientConfiguration.getMaximumConnectionsPerRoute() == 50 - assert httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5) + def 'Test http client configuration properties of data with custom and default values'() { + expect: 'properties are populated correctly for data' + with(httpClientConfiguration.dataServices) { + assert connectionTimeoutInSeconds == 123 + assert readTimeoutInSeconds == 789 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 100 + assert pendingAcquireMaxCount == 22 + assert maximumInMemorySizeInMegabytes == 7 + } + } + + def 'Test http client configuration properties of model with custom and default values'() { + expect: 'properties are populated correctly for model' + with(httpClientConfiguration.modelServices) { + assert connectionTimeoutInSeconds == 456 + assert readTimeoutInSeconds == 30 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 111 + assert pendingAcquireMaxCount == 44 + assert maximumInMemorySizeInMegabytes == 8 + } + } + + def 'Test http client configuration properties of health with default values'() { + expect: 'properties are populated correctly for health' + with(httpClientConfiguration.healthCheckServices) { + assert connectionTimeoutInSeconds == 30 + assert readTimeoutInSeconds == 30 + assert writeTimeoutInSeconds == 30 + assert maximumConnectionsTotal == 10 + assert pendingAcquireMaxCount == 5 + assert maximumInMemorySizeInMegabytes == 1 + } } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy deleted file mode 100644 index 74e342405..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ -package org.onap.cps.ncmp.api.impl.config - -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.web.client.RestTemplateBuilder -import org.springframework.http.MediaType -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.test.context.ContextConfiguration -import org.springframework.web.client.RestTemplate -import spock.lang.Specification - -@SpringBootTest -@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration]) -class NcmpConfigurationSpec extends Specification{ - - @Autowired - NcmpConfiguration.DmiProperties dmiProperties - - @Autowired - HttpClientConfiguration httpClientConfiguration - - def mockRestTemplateBuilder = new RestTemplateBuilder() - - def 'NcmpConfiguration Construction.'() { - expect: 'the system can create an instance' - new NcmpConfiguration() != null - } - - def 'DMI Properties.'() { - expect: 'properties are set to values in test configuration yaml file' - dmiProperties.authUsername == 'some-user' - dmiProperties.authPassword == 'some-password' - } - - def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() { - when: 'a rest template is created' - def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration) - then: 'the rest template is returned' - assert result instanceof RestTemplate - and: 'the rest template is created with httpclient5' - assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory - assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient; - and: 'a jackson media converter has been added' - def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1) - lastMessageConverter instanceof MappingJackson2HttpMessageConverter - and: 'the jackson media converters supports the expected media types' - lastMessageConverter.getSupportedMediaTypes() == [MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN]; - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy new file mode 100644 index 000000000..07395cf5b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/OpenTelemetryConfigSpec.groovy @@ -0,0 +1,81 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter +import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler +import org.spockframework.spring.SpringBean +import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer +import spock.lang.Shared +import spock.lang.Specification + +class OpenTelemetryConfigSpec extends Specification{ + + @Shared + @SpringBean + OpenTelemetryConfig openTelemetryConfig = new OpenTelemetryConfig() + + def setupSpec() { + openTelemetryConfig.tracingExporterEndpointUrl="http://tracingExporterEndpointUrl" + openTelemetryConfig.jaegerRemoteSamplerUrl="http://jaegerremotesamplerurl" + openTelemetryConfig.serviceId ="cps-application" + } + + def 'OpenTelemetryConfig Construction.'() { + expect: 'the system can create an instance' + new OpenTelemetryConfig() != null + } + + def 'OTLP Exporter creation with Grpc protocol'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createOtlpExporterGrpc() + then: 'an OTLP Exporter is created' + assert result instanceof OtlpGrpcSpanExporter + } + + def 'OTLP Exporter creation with HTTP protocol'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createOtlpExporterHttp() + then: 'an OTLP Exporter is created' + assert result instanceof OtlpHttpSpanExporter + and: + assert result.builder.endpoint=="http://tracingExporterEndpointUrl" + } + + def 'Jaeger Remote Sampler Creation'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.createJaegerRemoteSampler() + then: 'an OTLP Exporter is created' + assert result instanceof JaegerRemoteSampler + and: + assert result.delegate.type=="remoteSampling" + and: + assert result.delegate.url.toString().startsWith("http://jaegerremotesamplerurl") + } + + def 'Skipping Acutator endpoints'(){ + when: 'an OTLP exporter is created' + def result = openTelemetryConfig.skipActuatorEndpointsFromObservation() + then: 'an OTLP Exporter is created' + assert result instanceof ObservationRegistryCustomizer + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy index 16f27d081..4d3fd6616 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.kafka; +package org.onap.cps.ncmp.api.impl.config.kafka import io.cloudevents.CloudEvent import io.cloudevents.kafka.CloudEventDeserializer @@ -31,12 +31,14 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.support.serializer.JsonDeserializer import org.springframework.kafka.support.serializer.JsonSerializer +import org.springframework.test.context.TestPropertySource import spock.lang.Shared import spock.lang.Specification @SpringBootTest(classes = [KafkaProperties, KafkaConfig]) @EnableSharedInjection @EnableConfigurationProperties +@TestPropertySource(properties = ["cps.tracing.enabled=true"]) class KafkaConfigSpec extends Specification { @Shared diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy index e50652689..75db0bfe5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy @@ -46,4 +46,15 @@ class CmNotificationSubscriptionDeltaSpec extends Specification { } + def 'Find Delta of given list of predicates when it is an ongoing Cm Subscription'() { + given: 'A list of predicates' + def predicateList = [new DmiCmNotificationSubscriptionPredicate(['ch-1'].toSet(), DatastoreType.PASSTHROUGH_OPERATIONAL, ['a/1/'].toSet())] + and: 'its already present' + mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-1', 'a/1/') >>> true + when: 'getDelta is called' + def result = objectUnderTest.getDelta(predicateList) + then: 'verify correct delta is returned' + assert result.size() == 0 + } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy index cfb28a0ad..039a18949 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy @@ -25,8 +25,8 @@ import io.cloudevents.CloudEvent import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmHandle import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent -import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Data import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification @@ -43,7 +43,7 @@ class CmNotificationSubscriptionDmiInEventProducerSpec extends Specification { def subscriptionId = 'test-subscription-id' def dmiPluginName = 'test-dmiplugin' def eventType = 'subscriptionCreateRequest' - def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent(data: new Data(cmhandles: [new Cmhandle(cmhandleId: 'test-1', privateProperties: [:])])) + def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent(data: new Data(cmHandles: [new CmHandle(cmhandleId: 'test-1', privateProperties: [:])])) and: 'also we have target topic for dmiPlugin' objectUnderTest.cmNotificationSubscriptionDmiInEventTopic = 'dmiplugin-test-topic' when: 'the event is published' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy index 488879db7..9b0a48d93 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy @@ -36,7 +36,6 @@ import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncm import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.slf4j.LoggerFactory -import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -108,9 +107,9 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe and: 'correct number of calls to publish the ncmp out event to client' 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent('sub-1', 'subscriptionCreateResponse', _, false) where: 'the following parameters are used' - scenario | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls - 'Accepted Status' | CmNotificationSubscriptionStatus.ACCEPTED | '1' || 1 | 1 - 'Rejected Status' | CmNotificationSubscriptionStatus.REJECTED | '2' || 1 | 0 + scenario | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls + 'Accepted Status' | CmNotificationSubscriptionStatus.ACCEPTED | '1' || 1 | 1 + 'Rejected Status' | CmNotificationSubscriptionStatus.REJECTED | '104' || 1 | 0 } def getLoggingEvent() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy index 9c84c51b2..01a92c02f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy @@ -72,19 +72,40 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe .withSource(URI.create('some-resource')) .withExtension('correlationid', 'test-cmhandle1').build() def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) - and: 'notifications are enabled' - objectUnderTest.notificationFeatureEnabled = true when: 'the valid event is consumed' objectUnderTest.consumeSubscriptionEvent(consumerRecord) then: 'an event is logged with level INFO' def loggingEvent = getLoggingEvent() assert loggingEvent.level == Level.INFO and: 'the log indicates the task completed successfully' - assert loggingEvent.formattedMessage == 'Subscription for source some-resource with subscription id test-id ...' + assert loggingEvent.formattedMessage == 'Subscription create request for source some-resource with subscription id test-id ...' and: 'the subscription handler service is called once' - 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(_) + 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest('test-id',_) } + def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() { + given: 'a cmNotificationSubscription event' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def testCloudEventSent = CloudEventBuilder.v1() + .withData(objectMapper.writeValueAsBytes(testEventSent)) + .withId('sub-id') + .withType('subscriptionDeleteRequest') + .withSource(URI.create('some-resource')) + .withExtension('correlationid', 'test-cmhandle1').build() + def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) + when: 'the valid event is consumed' + objectUnderTest.consumeSubscriptionEvent(consumerRecord) + then: 'an event is logged with level INFO' + def loggingEvent = getLoggingEvent() + assert loggingEvent.level == Level.INFO + and: 'the log indicates the task completed successfully' + assert loggingEvent.formattedMessage == 'Subscription delete request for source some-resource with subscription id test-id ...' + and: 'the subscription handler service is called once' + 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionDeleteRequest('test-id',_) + } + + def getLoggingEvent() { return logger.list[1] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy index 43568be50..393432fc5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy @@ -27,10 +27,10 @@ import org.apache.kafka.clients.consumer.ConsumerRecord import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -133,10 +133,10 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { assert resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].targetCmHandleIds == ['ch4'].toSet() and: 'the list of xpath for each is correct' assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[0].xpaths - && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].xpaths == ['/x1/y1','x2/y2'].toSet() + && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].xpaths == ['/x1/y1','x2/y2'].toSet() assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[1].xpaths - && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].xpaths == ['/x3/y3','x4/y4'].toSet() + && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].xpaths == ['/x3/y3','x4/y4'].toSet() } def 'Get map for cm handle IDs by DMI service name'() { @@ -164,7 +164,7 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { } def 'Persist Cache into database per dmi'() { - given: 'populate cache' + given: 'populated cache' def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates() def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId() objectUnderTest.add(subscriptionId, predicates) @@ -174,15 +174,26 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { 4 * mockCmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(_,_,_,subscriptionId) } + def 'Remove subscription from database per dmi'() { + given: 'populated cache' + def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates() + def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId() + objectUnderTest.add(subscriptionId, predicates) + when: 'subscription is persisted in database' + objectUnderTest.removeFromDatabasePerDmi(subscriptionId,'dmi-1') + then: 'persistence service is called the correct number of times per dmi' + 4 * mockCmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(_,_,_,subscriptionId) + } + def setUpTestEvent(){ def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) def testCloudEventSent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventSent)) - .withId('subscriptionCreated') - .withType('subscriptionCreated') - .withSource(URI.create('some-resource')) - .withExtension('correlationid', 'test-cmhandle1').build() + .withData(objectMapper.writeValueAsBytes(testEventSent)) + .withId('subscriptionCreated') + .withType('subscriptionCreated') + .withSource(URI.create('some-resource')) + .withExtension('correlationid', 'test-cmhandle1').build() def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent) def cloudEvent = consumerRecord.value() @@ -191,10 +202,10 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { def initialiseMockInventoryPersistenceResponses(){ mockInventoryPersistence.getYangModelCmHandles(['ch1','ch2']) - >> [yangModelCmHandle1, yangModelCmHandle2] + >> [yangModelCmHandle1, yangModelCmHandle2] mockInventoryPersistence.getYangModelCmHandles(['ch3','ch4']) - >> [yangModelCmHandle3, yangModelCmHandle4] + >> [yangModelCmHandle3, yangModelCmHandle4] } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy index 763aedaa0..0c9e3b691 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy @@ -22,8 +22,8 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL @@ -48,10 +48,10 @@ class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification { when: 'we try to map the values' def result = objectUnderTest.toCmNotificationSubscriptionDmiInEvent(dmiCmNotificationSubscriptionPredicates) then: 'it contains correct cm notification subscription cmhandle object' - assert result.data.cmhandles.cmhandleId.containsAll(['ch-1', 'ch-2']) - assert result.data.cmhandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']]) + assert result.data.cmHandles.cmhandleId.containsAll(['ch-1', 'ch-2']) + assert result.data.cmHandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']]) and: 'also has the correct dmi cm notification subscription predicates' assert result.data.predicates.targetFilter.containsAll([['ch-1'], ['ch-2']]) } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy index 98b4ee267..982150ec0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy @@ -25,10 +25,10 @@ import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscripti import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate +import org.onap.cps.ncmp.api.impl.operations.DatastoreType import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent @@ -50,13 +50,16 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ mockCmNotificationSubscriptionEventsHandler, mockDmiCmNotificationSubscriptionCacheHandler) def testSubscriptionDetailsMap = ["dmi-1":new DmiCmNotificationSubscriptionDetails([], CmNotificationSubscriptionStatus.PENDING)] - def testListOfDeltaPredicates = [] def 'Consume valid and unique CmNotificationSubscriptionNcmpInEvent create message'() { given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def testListOfDeltaPredicates = [new DmiCmNotificationSubscriptionPredicate(['ch1'].toSet(), DatastoreType.PASSTHROUGH_OPERATIONAL, ['/a/b'].toSet())] mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() and: 'the cache handler returns for relevant subscription id' 1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap and: 'the delta predicates is returned' @@ -66,7 +69,7 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ 1 * mockCmNotificationSubscriptionMappersHandler .toCmNotificationSubscriptionDmiInEvent(testListOfDeltaPredicates) >> testDmiInEvent when: 'the valid and unique event is consumed' - objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) + objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates) then: 'the subscription cache handler is called once' 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id',_) and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters' @@ -77,21 +80,68 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ "test-id", "subscriptionCreateResponse", null, true) } + def 'Consume valid and Overlapping Cm Notification Subscription NcmpIn Event'() { + given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + def noDeltaPredicates = [] + mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true + and: 'the cache handler returns for relevant subscription id' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap + and: 'the delta predicates is returned' + 1 * mockCmNotificationSubscriptionDelta.getDelta(_) >> noDeltaPredicates + when: 'the valid and unique event is consumed' + objectUnderTest.processSubscriptionCreateRequest('test-id', noDeltaPredicates) + then: 'the subscription cache handler is called once' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id', _) + and: 'the subscription details are updated in the cache' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi('test-id', _, CmNotificationSubscriptionStatus.ACCEPTED) + and: 'we schedule to send the response after configured time from the cache' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + "test-id", "subscriptionCreateResponse", null, true) + } + def 'Consume valid and but non-unique CmNotificationSubscription create message'() { given: 'a cmNotificationSubscriptionNcmp in event' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('test-id') >> false + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() and: 'the NCMP out in event mapper returns an event for rejected request' def testNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent() 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( "test-id",_) >> testNcmpOutEvent when: 'the valid but non-unique event is consumed' - objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) + objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates) then: 'the events handler method to publish DMI event is never called' 0 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(_,_,_,_) and: 'the events handler method to publish NCMP out event is called once' 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( 'test-id', 'subscriptionCreateResponse', testNcmpOutEvent, false) } + + def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() { + given: 'a cmNotificationSubscriptionNcmp in event for delete' + def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') + def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) + and: 'relevant details is extracted from the event' + def subscriptionId = testEventConsumed.getData().getSubscriptionId() + def predicates = testEventConsumed.getData().getPredicates() + and: 'the cache handler returns for relevant subscription id' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.get('test-id') >> testSubscriptionDetailsMap + when: 'the valid and unique event is consumed' + objectUnderTest.processSubscriptionDeleteRequest(subscriptionId, predicates) + then: 'the subscription cache handler is called once' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id', predicates) + and: 'the mapper handler to get DMI in event is called once' + 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent(_) + and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters' + testSubscriptionDetailsMap.size() * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent( + 'test-id', 'dmi-1', 'subscriptionDeleteRequest', _) + and: 'we schedule to send the response after configured time from the cache' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + 'test-id', 'subscriptionDeleteResponse', null, true) + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy index b51ecb0cf..281ec4f7e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2024 Nordix Foundation. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +24,8 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service import org.onap.cps.utils.ContentType import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE; +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE; import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH; - import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService import org.onap.cps.ncmp.api.impl.operations.DatastoreType @@ -48,7 +48,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']"; and: 'datanodes optionally returned' 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode + cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode when: 'we check for an ongoing cm subscription' def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path') then: 'we get expected response' @@ -64,7 +64,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted('some-sub') and: 'relevant datanodes are returned' 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> - dataNodes + dataNodes when: 'a subscription ID is tested for uniqueness' def result = objectUnderTest.isUniqueSubscriptionId('some-sub') then: 'result is as expected' @@ -79,7 +79,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification given: 'a valid cm subscription path query' def cpsPathQuery =CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y') and: 'a dataNode exists for the given cps path query' - mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', + mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] when: 'the method to add/update cm notification subscription is called' objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId') @@ -88,18 +88,18 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification 'NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters', - objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _) + objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _,ContentType.JSON) } def 'Add new cm notification subscription for #datastoreType'() { given: 'a valid cm subscription path query' def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y') - def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') + def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') and: 'a parent node xpath for the cm subscription path above' def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles' and: 'a datanode does not exist for cm subscription path query' mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cmSubscriptionCpsPathQuery, + cmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [] and: 'a datanode does not exist for the given cm handle subscription path query' mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', @@ -124,7 +124,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def 'Add new cm notification subscription when xpath does not exist for existing subscription cm handle'() { given: 'a valid cm subscription path query' def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y') - def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') + def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1') and: 'a parent node xpath for given cm handle for subscription path above' def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles/cm-handle[@id=\'%s\']/filters' and: 'a datanode does not exist for cm subscription path query' @@ -157,19 +157,36 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification then: 'the list of subscribers is updated' 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters', - objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _) + objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _, ContentType.JSON) } - def 'Removing last ongoing subscription for datastore, cmhandle and xpath'(){ + def 'Removing last ongoing subscription for datastore and cmhandle and xpath'(){ given: 'a subscription exists when queried but has only 1 subscriber' - def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y') - mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] + mockCpsQueryService.queryDataNodes( + 'NCMP-Admin', + 'cm-data-subscriptions', + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y'), + FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])] + and: 'the #scenario' + mockCpsQueryService.queryDataNodes( + 'NCMP-Admin', + 'cm-data-subscriptions', + CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted('ncmp-datastore:passthrough-running', 'ch-1'), + FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [new DataNode(childDataNodes: listOfChildNodes)] when: 'that last ongoing subscription is removed' objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1') then: 'the subscription with empty subscriber list is removed' 1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions', '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']', _) + and: 'method call to delete the cm handle is called the correct number of times' + numberOfCallsToDeleteCmHandle * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions', + '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']', + _) + where: + scenario | listOfChildNodes || numberOfCallsToDeleteCmHandle + 'cm handle in same datastore is used for other subscriptions' | [new DataNode()] || 0 + 'cm handle in same datastore is NOT used for other subscriptions' | [] || 1 } + }
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index 3ae95209f..1adf01c55 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -20,19 +20,19 @@ package org.onap.cps.ncmp.api.impl.events.lcm -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED - -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED + class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy index bef2963cf..36024b74e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy @@ -20,17 +20,17 @@ package org.onap.cps.ncmp.api.impl.events.lcm -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY - import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.Values +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY + class LcmEventsCreatorSpec extends Specification { LcmEventHeaderMapper lcmEventsHeaderMapper = Mappers.getMapper(LcmEventHeaderMapper) @@ -190,4 +190,4 @@ class LcmEventsCreatorSpec extends Specification { 'blank target and existing values' | '' | '' | '' | '' | '' | '' 'new target value and blank existing values' | '' | 'someAlternateId' | '' | 'someAlternateId' | '' | 'someDataProducerIdentifier' } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy new file mode 100644 index 000000000..7e364c97c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations + +import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException +import spock.lang.Specification + +class DatastoreTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + DatastoreType.fromDatastoreName(datastoreName) == expectedEnum + where: 'the following datastore names are used' + datastoreName || expectedEnum + 'ncmp-datastore:operational' || DatastoreType.OPERATIONAL + 'ncmp-datastore:passthrough-running' || DatastoreType.PASSTHROUGH_RUNNING + 'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + DatastoreType.fromDatastoreName('unknown') + then: 'an invalid datastore exception is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains('unknown is an invalid datastore') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index eb6c7a0f4..1e877f4a8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy @@ -23,13 +23,13 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -38,29 +38,27 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration +import reactor.core.publisher.Mono import spock.lang.Shared -import java.util.concurrent.TimeoutException - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA @SpringBootTest -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, NcmpConfiguration.DmiProperties, DmiDataOperations]) +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) class DmiDataOperationsSpec extends DmiOperationsBaseSpec { - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = Mock() def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:" def NO_TOPIC = null def NO_REQUEST_ID = null def NO_AUTH_HEADER = null + @Shared def OPTIONS_PARAM = '(a=1,b=2)' @@ -77,23 +75,24 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - def expectedUrl = dmiServiceBaseUrl + "${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" - mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl + def responseFromDmi = Mono.just(new ResponseEntity<Object>('{some-key:some-value}', HttpStatus.OK)) + def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" + def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get resource data is invoked' def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier) - def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) + def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block() then: 'the result is the response from the DMI service' - assert result == responseFromDmi + assert result.body == '{some-key:some-value}' + assert result.statusCode.'2xxSuccessful' where: 'the following parameters are used' - scenario | dmiProperties | dataStore | options || expectedJson | expectedDatastoreInUrl | expectedOptionsInUrl - 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '' - 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '' - 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}' | 'passthrough-running' | '&options=(a=1,b=2)' - 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running' | '&options=(a=1,b=2)' + scenario | dmiProperties | dataStore | options || expectedProperties | expectedDatastoreInUrl | expectedOptionsInUrl + 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' } def 'Execute (async) data operation from DMI service.'() { @@ -103,49 +102,48 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] and: 'a positive response from DMI service when it is called with valid request parameters' - def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED) - def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name" - def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]}' - mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDataOperationRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl - when: 'get resource data for group of cm handles are invoked' + def responseFromDmi = Mono.just(new ResponseEntity<Object>(HttpStatus.ACCEPTED)) + def expectedDmiBatchResourceDataUrl = "someServiceName/dmi/v1/data?requestId=requestId&topic=my-topic-name" + def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, _, READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get resource data for group of cm handles is invoked' objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - then: 'the post operation was called and ncmp generated dmi request body json args' - 1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) + then: 'the post operation was called with the expected URL and JSON request body' + 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) } - def 'Execute (async) data operation from DMI service for #scenario.'() { - given: 'data operation request body and dmi resource url' - def dmiDataOperation = DmiDataOperation.builder().operationId('some-operation-id').build() - dmiDataOperation.getCmHandles().add(CmHandle.builder().id('some-cm-handle-id').build()) - def dmiDataOperationResourceDataUrl = "http://dmi-service-name:dmi-port/dmi/v1/data?topic=my-topic-name&requestId=some-request-id" + def 'Execute (async) data operation from DMI service with Exception.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] + and: 'the published cloud event will be captured' def actualDataOperationCloudEvent = null - when: 'exception occurs after sending request to dmi service' - objectUnderTest.handleTaskCompletionException(new Throwable(exception), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation)) - then: 'a cloud event is published' - eventsPublisher.publishCloudEvent('my-topic-name', 'some-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } - and: 'the event contains the expected error details' + eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } + and: 'a DMI client request exception is thrown when DMI service is called' + mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } + when: 'attempt to get resource data for group of cm handles is invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) + then: 'the event contains the expected error details' def eventDataValue = extractDataValue(actualDataOperationCloudEvent) - assert eventDataValue.operationId == dmiDataOperation.operationId - assert eventDataValue.ids == dmiDataOperation.cmHandles.id - assert eventDataValue.statusCode == responseCode.code - assert eventDataValue.statusMessage == responseCode.message - where: 'the following exceptions are occurred' - scenario | exception || responseCode - 'http client request exception' | new HttpClientRequestException('error-message', 'error-details', HttpStatus.SERVICE_UNAVAILABLE.value()) || UNABLE_TO_READ_RESOURCE_DATA - 'timeout exception' | new TimeoutException() || DMI_SERVICE_NOT_RESPONDING + assert eventDataValue.statusCode == '108' + assert eventDataValue.statusMessage == UNKNOWN_ERROR.message + and: 'the event contains the correct operation details' + assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId + assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds } def 'call get all resource data.'() { - given: 'the system returns a cm handle with a sample property' - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) + given: 'the system returns a cm handle with a sample property and sample module set tag' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag') and: 'a positive response from DMI service when it is called with the expected parameters' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ, null) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl + def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi when: 'get resource data is invoked' - def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.datastoreName, cmHandleId, NO_REQUEST_ID) + def result = objectUnderTest.getAllResourceDataFromDmi(cmHandleId, NO_REQUEST_ID) then: 'the result is the response from the DMI service' assert result == responseFromDmi } @@ -154,11 +152,10 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) and: 'a positive response from DMI service when it is called with the expected parameters' - def expectedUrl = dmiServiceBaseUrl + "passthrough-running?resourceIdentifier=${resourceIdentifier}" - def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"}}' + def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${resourceIdentifier}" + def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl - mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi when: 'write resource method is invoked' def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) then: 'the result is the response from the DMI service' @@ -169,7 +166,29 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { UPDATE || 'update' } + def 'State Ready validation'() { + given: ' a yang model cm handle' + populateYangModelCmHandle([] ,'') + when: 'Validating State of #cmHandleState' + def caughtException = null + try { + objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState) + } catch (Exception e) { + caughtException = e + } + then: 'only when not ready a exception is thrown' + if (expecteException) { + assert caughtException.details.contains('not in READY state') + } else { + assert caughtException == null + } + where: ' the following states are used' + cmHandleState || expecteException + CmHandleState.READY || false + CmHandleState.ADVISED || true + } + def extractDataValue(actualDataOperationCloudEvent) { - return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent.class).data.responses[0] + return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0] } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index 9aab46747..9ab52b946 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -21,9 +21,12 @@ package org.onap.cps.ncmp.api.impl.operations +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL + import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.onap.cps.ncmp.api.impl.config.DmiProperties import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -34,10 +37,8 @@ import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration import spock.lang.Shared -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ - @SpringBootTest -@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations]) +@ContextConfiguration(classes = [DmiProperties, DmiModelOperations]) class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @Shared @@ -58,8 +59,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules" def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) - >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result consists of expected module references' @@ -72,7 +72,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'any response from DMI service when it is called with the expected parameters' // TODO (toine): production code ignores any error code from DMI, this should be improved in future def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT) - mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result is empty' @@ -90,7 +90,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with tha expected parameters' def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) @@ -109,7 +109,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) @@ -125,7 +125,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'a positive response from DMI service when it is called with tha expected parameters' // TODO (toine): production code ignores any error code from DMI, this should be improved in future def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT) - mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result is empty' @@ -141,7 +141,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' @@ -159,9 +159,9 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval([], moduleSetTag) and: 'a positive response from DMI service when it is called with the expected moduleSetTag' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', - READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', + READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result is the response from DMI service' @@ -180,7 +180,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'no resources are returned' assert result == [:] and: 'no request is sent to DMI' - 0 * mockDmiRestClient.postOperationWithJsonData(*_) + 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) } def 'Retrieving yang resources from DMI with null DMI properties.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy index 72a0f2f11..f224a5cc0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,10 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.spi.utils.CpsValidator +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.spockframework.spring.SpringBean import spock.lang.Shared import spock.lang.Specification @@ -44,16 +41,11 @@ abstract class DmiOperationsBaseSpec extends Specification { @SpringBean InventoryPersistence mockInventoryPersistence = Mock() - def mockCpsValidator = Mock(CpsValidator) - @SpringBean ObjectMapper spyObjectMapper = Spy() - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties(), mockCpsValidator) - def yangModelCmHandle = new YangModelCmHandle() - def static dmiServiceName = 'some service name' + def static dmiServiceName = 'someServiceName' def static cmHandleId = 'some-cm-handle' def static resourceIdentifier = 'parent/child' @@ -68,7 +60,7 @@ abstract class DmiOperationsBaseSpec extends Specification { } def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { - populateYangModelCmHandle(dmiProperties, "") + populateYangModelCmHandle(dmiProperties, '') mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy new file mode 100644 index 000000000..d31b8d4fd --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations + +import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException +import spock.lang.Specification + +class OperationTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + OperationType.fromOperationName(operationName) == expectedEnum + where: 'the following datastore names are used' + operationName || expectedEnum + 'read' || OperationType.READ + 'create' || OperationType.CREATE + 'update' || OperationType.UPDATE + 'patch' || OperationType.PATCH + 'delete' || OperationType.DELETE + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + OperationType.fromOperationName('unknown') + then: 'an invalid operation exception is thrown' + def thrown = thrown(InvalidOperationException) + assert thrown.message.contains('unknown is an invalid operation') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy index 886648a7e..65ab7a7fd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy @@ -21,8 +21,8 @@ package org.onap.cps.ncmp.api.impl.trustlevel import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification class TrustLevelManagerSpec extends Specification { 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 446126be9..f5835ff85 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 @@ -20,24 +20,21 @@ package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability -import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.impl.client.DmiRestClient import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService import spock.lang.Specification class DmiPluginWatchDogSpec extends Specification { def mockDmiRestClient = Mock(DmiRestClient) - def mockNetworkCmProxyDataService = Mock(NetworkCmProxyDataService) + def mockCmHandleQueryService = Mock(CmHandleQueryService) def mockTrustLevelManager = Mock(TrustLevelManager) def trustLevelPerDmiPlugin = [:] - def objectUnderTest = new DmiPluginWatchDog(mockDmiRestClient, - mockNetworkCmProxyDataService, - mockTrustLevelManager, - trustLevelPerDmiPlugin) + def objectUnderTest = new DmiPluginWatchDog(mockDmiRestClient, mockCmHandleQueryService, mockTrustLevelManager, trustLevelPerDmiPlugin) def 'watch dmi plugin health status for #dmiHealhStatus'() { given: 'the cache has been initialised and "knows" about dmi-1' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy index 1e843676b..5be966b0f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy @@ -20,9 +20,9 @@ package org.onap.cps.ncmp.api.impl.utils -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy index 9f84fb073..2e0469430 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy @@ -20,11 +20,11 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeBaseSpec extends Specification { def leaves1 = [status:'PENDING', cmHandleId:'CMHandle3', details:'Subscription forwarded to dmi plugin'] as Map diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy index 05e5f5898..9481613d4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeHelperSpec extends DataNodeBaseSpec { def 'Get data node leaves as expected from a nested data node.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy index fbf2c3d78..69d08e3de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,76 +20,67 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING - -import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService -import org.onap.cps.spi.utils.CpsValidator -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import spock.lang.Specification class DmiServiceUrlBuilderSpec extends Specification { - static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', - 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') - - NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() - - def mockCpsValidator = Mock(CpsValidator) + def objectUnderTest = new DmiServiceUrlBuilder() - def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties, mockCpsValidator) - - def setup() { - dmiProperties.dmiBasePath = 'dmi' + def 'Build URI with (variable) path segments and parameters.'() { + given: 'the URI details are given to the builder' + objectUnderTest.pathSegment(segment1) + objectUnderTest.variablePathSegment('myVariableSegment','someValue') + objectUnderTest.pathSegment(segment2) + objectUnderTest.queryParameter('param1', paramValue1) + objectUnderTest.queryParameter('param2', paramValue2) + objectUnderTest.queryParameter('param3', null) + objectUnderTest.queryParameter('param4', '') + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'the URI is correct (segments are in correct order) ' + assert result == expectedUri + where: 'following URI details are used' + segment1 | segment2 | paramValue1 | paramValue2 || expectedUri + 'segment1' | 'segment2' | '123' | 'abc' || 'myDmiServer/myBasePath/v1/segment1/someValue/segment2?param1=123¶m2=abc' + 'segment2' | 'segment1' | 'abc' | '123' || 'myDmiServer/myBasePath/v1/segment2/someValue/segment1?param1=abc¶m2=123' } - def 'Create the dmi service url with #scenario.'() { - given: 'uri variables' - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'query params' - def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'service url is generated as expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | topic | resourceId || expectedDmiServiceUrl - 'With valid resourceId' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty resourceId' | 'topicParamInQuery' | '' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty dmi base path' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery' + def 'Build URI with special characters in path segments.'() { + given: 'the path segments are given to the builder' + objectUnderTest.pathSegment(segment) + objectUnderTest.variablePathSegment('myVariableSegment', variableSegmentValue) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only teh characters that cause issues in path segments issues are encoded' + assert result == expectedUri + where: 'following variable path segments are used' + segment | variableSegmentValue || expectedUri + 'some/special?characters=are\\encoded' | 'my/variable/segment' || 'myDmiServer/myBasePath/v1/some%2Fspecial%3Fcharacters=are%5Cencoded/my%2Fvariable%2Fsegment' + 'but=some&are:not-!' | 'my&variable:segment' || 'myDmiServer/myBasePath/v1/but=some&are:not-!/my&variable:segment' } - def 'Populate dmi data store url #scenario.'() { - given: 'uri variables are created' - dmiProperties.dmiBasePath = dmiBasePath - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'null query params' - def uriQueries = objectUnderTest.populateQueryParams(null, null, null) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'the created dmi service url matches the expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | decription | dmiBasePath || expectedDmiServiceUrl - 'with base path / ' | 'Invalid base path as it starts with /' | '/dmi' || 'dmiServiceName//dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' - 'without base path / ' | 'Valid path as it does not starts with /' | 'dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' + def 'Build URI with special characters in query parameters.'() { + given: 'the query parameter is given to the builder' + objectUnderTest.queryParameter(paramName, value) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only the characters (in the name and value) that cause issues in query parameters are encoded' + assert result == expectedUri + where: 'the following query parameters are used' + paramName | value || expectedUri + 'my¶m' | 'some?special&characters=are\\encoded' || 'myDmiServer/myBasePath/v1?my%26param=some?special%26characters%3Dare%5Cencoded' + 'my-param' | 'but/some:are-not-!' || 'myDmiServer/myBasePath/v1?my-param=but/some:are-not-!' } - def 'Bath request Url creation.'() { - given: 'the required path parameters' - def batchRequestUriVariables = [dmiServiceName: 'some-service', dmiBasePath: 'testBase', cmHandleId: '123'] - and: 'the relevant query parameters' - def batchRequestQueryParams = objectUnderTest.getDataOperationRequestQueryParams('some topic', 'some id') - when: 'a URL is created' - def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables) - then: 'it is formed correctly' - assert result.toString() == 'some-service/testBase/v1/data?topic=some topic&requestId=some id' + def 'Build URI with empty query parameters.'() { + when: 'the query parameter is given to the builder' + objectUnderTest.queryParameter('param', value) + and: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'no parameter gets added' + assert result == 'myDmiServer/myBasePath/v1' + where: 'the following parameter values are used' + value << [ null, '', ' ' ] } - def 'Populate batch uri variables.'() { - expect: 'Populate batch uri variables returns a map with given service name and base path from setup' - assert objectUnderTest.populateDataOperationRequestUriVariables('some service') == [dmiServiceName: 'some service', dmiBasePath: 'dmi' ] - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy new file mode 100644 index 000000000..c72eb9e4c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.utils + +import spock.lang.Specification +import java.time.Year + +class EventDateTimeFormatterSpec extends Specification { + + def 'Get ISO formatted date and time.' () { + expect: 'iso formatted date and time starts with current year' + assert EventDateTimeFormatter.getCurrentIsoFormattedDateTime().startsWith(String.valueOf(Year.now())) + } + + def 'Convert date time from string to OffsetDateTime type.'() { + when: 'date time as a string is converted to OffsetDateTime type' + def result = EventDateTimeFormatter.toIsoOffsetDateTime('2024-05-28T18:28:02.869+0100') + then: 'the result convert back back to a string is the same as the original timestamp (except the format of timezone offset)' + assert result.toString() == '2024-05-28T18:28:02.869+01:00' + } + + def 'Convert blank string.' () { + expect: 'converting a blank string result in null' + assert EventDateTimeFormatter.toIsoOffsetDateTime(' ') == null + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy index 0ae348c07..3f0d98934 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.api.impl.utils +import org.onap.cps.ncmp.impl.utils.YangDataConverter import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -34,7 +35,7 @@ class YangDataConverterSpec extends Specification{ def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties]) when: 'the dataNode is converted' def yangModelCmHandle = - YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) + YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) then: 'the converted object has the correct id' assert yangModelCmHandle.id == 'sample-id' and: 'the additional (dmi, private) properties are included' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy index b7fa44925..ee117160c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy @@ -1,3 +1,23 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (c) 2023-2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.utils.context import com.fasterxml.jackson.databind.ObjectMapper diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy index 8df27bb62..d8d04d44a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy @@ -26,26 +26,27 @@ import io.cloudevents.kafka.CloudEventDeserializer import io.cloudevents.kafka.impl.KafkaHeaders import org.apache.kafka.clients.consumer.KafkaConsumer import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.NcmpResponseStatus +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation +import org.onap.cps.ncmp.api.impl.operations.OperationType import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.ContextConfiguration +import org.springframework.util.LinkedMultiValueMap import java.time.Duration -import java.util.concurrent.TimeoutException import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, ObjectMapper]) +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext]) class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { def static clientTopic = 'my-topic-name' @@ -57,9 +58,6 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { @SpringBean EventsPublisher eventPublisher = new EventsPublisher<CloudEvent>(legacyEventKafkaTemplate, cloudEventKafkaTemplate) - @Autowired - ObjectMapper objectMapper - def 'Process per data operation request with #serviceName.'() { given: 'data operation request with 3 operations' def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') @@ -90,6 +88,23 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] } + def 'Process one data operation request with #serviceName and Module Set Tag set.'() { + given: 'data operation request' + def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) + and: '1 known cm handles: ch1-dmi1' + def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle() + when: 'data operation request is processed' + def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) + and: 'converted to a json node' + def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1'] + def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles + then: 'it contains the correct operation details' + assert cmHandlesInRequestBody.size() == 1 + assert cmHandlesInRequestBody[0].id == 'ch1-dmi1' + assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1' + } + def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() { given: 'consumer subscribing to client topic' def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer)) @@ -118,34 +133,10 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson } - def 'Publish error response for entire data operations request when async task fails'() { - given: 'consumer subscribing to client topic' - def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties(consumerGroupId, CloudEventDeserializer)) - cloudEventKafkaConsumer.subscribe([clientTopic]) - and: 'data operation request having non-ready and non-existing cm handle ids' - def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) - when: 'an error occurs for the entire data operations request' - ResourceDataOperationRequestUtils.handleAsyncTaskCompletionForDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, exceptionThrown) - and: 'subscribed client specified topic is polled and first record is selected' - def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last() - def dataOperationResponseEvent = toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) - then: 'data operation response event response size is 3' - dataOperationResponseEvent.data.responses.size() == 3 - and: 'all 3 have the expected error code' - dataOperationResponseEvent.data.responses.each { - assert it.statusCode == errorReportedToClientTopic.code - } - where: - scenario | exceptionThrown | consumerGroupId || errorReportedToClientTopic - 'task timed out' | new TimeoutException() | 'test-2' || NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING - 'unspecified error' | new RuntimeException() | 'test-3' || NcmpResponseStatus.UNKNOWN_ERROR - } - static def getYangModelCmHandles() { def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build() - def advisedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).withLastUpdatedTimeNow().build() + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build() return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), @@ -156,4 +147,19 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) ] } + + static def getYangModelCmHandlesForOneCmHandle() { + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)] + } + + def mockAndPopulateErrorMap(errorReportedToClientTopic) { + def dmiDataOperation = DmiDataOperation.builder().operation(OperationType.fromOperationName('read')) + .operationId('some-op-id').datastore('ncmp-datastore:passthrough-operational') + .options('some-option').resourceIdentifier('some-resource-identifier').build() + def cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>() + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(errorReportedToClientTopic, ['some-cm-handle-id'])) + return cmHandleIdsPerResponseCodesPerOperation + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy index 7803ae319..055a6e744 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy @@ -19,16 +19,17 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.models +package org.onap.cps.ncmp.api.inventory.models + +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import spock.lang.Specification + +import java.util.stream.Collectors import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import spock.lang.Specification -import java.util.stream.Collectors - class CmHandleRegistrationResponseSpec extends Specification { def 'Successful cm-handle Registration Response'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy index 777b505b4..dfed3c787 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy @@ -19,13 +19,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.api.inventory.models -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory + +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy index d8beba8de..f6e4c8870 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy @@ -18,20 +18,20 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.api.inventory.models import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import spock.lang.Specification + import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational +import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores +import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent import static org.springframework.util.StringUtils.trimAllWhitespace diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy index 0a0c84e25..068997591 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy @@ -1,4 +1,5 @@ -package org.onap.cps.ncmp.api.models +package org.onap.cps.ncmp.api.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy index dc471e64f..15526a0c8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy @@ -18,21 +18,20 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.ConditionProperties import spock.lang.Specification -class RestQueryParametersValidatorSpec extends Specification { - +class CmHandleQueryParametersValidatorSpec extends Specification { def 'CM Handle Query validation: empty query.'() { given: 'a cm handle query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) then: 'data validation exception is not thrown' noExceptionThrown() } @@ -45,7 +44,7 @@ class RestQueryParametersValidatorSpec extends Specification { condition.conditionParameters = [['key':'value']] cmHandleQueryParameters.cmHandleQueryParameters = [condition] when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) then: 'data validation exception is not thrown' noExceptionThrown() } @@ -58,17 +57,17 @@ class RestQueryParametersValidatorSpec extends Specification { condition.conditionParameters = conditionParameters cmHandleQueryParameters.cmHandleQueryParameters = [condition] when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) then: 'a data validation exception is thrown' def thrown = thrown(DataValidationException) and: 'the exception details contain the correct significant term ' - thrown.details.contains(expectedWordInDetails) + assert thrown.details.contains(expectedWordInDetails) where: scenario | conditionName | conditionParameters || expectedWordInDetails 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' 'no condition name' | '' | [['key': 'value']] || 'conditionName' + 'empty conditions' | 'validConditionName' | [] || 'conditionsParameters' 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' - 'empty conditions' | 'validConditionName' | [[:]] || 'conditionsParameter' 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' } @@ -77,14 +76,14 @@ class RestQueryParametersValidatorSpec extends Specification { given: 'a condition property' def conditionProperty = [moduleName: 'value'] when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) then: 'data validation exception is not thrown' noExceptionThrown() } def 'CM Handle Query validation: validate module name condition properties - #scenario.'() { when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) then: 'a data validation exception is thrown' thrown(DataValidationException) where: @@ -95,7 +94,7 @@ class RestQueryParametersValidatorSpec extends Specification { def 'Validate CmHandle where an exception is thrown due to #scenario.'() { when: 'the validator is called on a cps path condition property' - RestQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) + CmHandleQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) then: 'a data validation exception is thrown' def e = thrown(DataValidationException) and: 'exception message matches the expected message' @@ -109,12 +108,12 @@ class RestQueryParametersValidatorSpec extends Specification { def 'No conditions.'() { expect: 'no conditions always returns true' - RestQueryParametersValidator.validateCpsPathConditionProperties([:]) == true + CmHandleQueryParametersValidator.validateCpsPathConditionProperties([:]) == true } def 'Validate CmHandle where #scenario.'() { when: 'the validator is called on a cps path condition property' - def result = RestQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) + def result = CmHandleQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) then: 'the expected boolean value is returned' result == expectedBoolean where: 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/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 2f9d26494..52ddcfb6c 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/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -19,21 +19,23 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory +package org.onap.cps.ncmp.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 org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.utils.CpsValidator import spock.lang.Shared import spock.lang.Specification -class CmHandleQueriesImplSpec extends Specification { +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class CmHandleQueryServiceImplSpec extends Specification { def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) @@ -43,7 +45,7 @@ class CmHandleQueriesImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def objectUnderTest = new CmHandleQueriesImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) + def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) @Shared def static sampleDataNodes = [new DataNode()] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy index 260772714..0a5b4f4ec 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl +package org.onap.cps.ncmp.impl.inventory import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger @@ -28,13 +28,13 @@ import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.slf4j.LoggerFactory import spock.lang.Specification @@ -42,22 +42,22 @@ import spock.lang.Specification import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -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.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { +class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockCpsDataService = Mock(CpsDataService) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) def mockAlternateIdChecker = Mock(AlternateIdChecker) - def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) + def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) def logger = Spy(ListAppender<ILoggingEvent>) void setup() { - def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)) + def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)) setupLogger.addAppender(logger) setupLogger.setLevel(Level.DEBUG) logger.start() @@ -66,7 +66,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders() } def static cmHandleId = 'myHandle1' @@ -209,7 +209,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { when: 'cm handle properties is updated' def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) then: 'the update is delegated to cps data service with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> assert args[3].contains('alt-1') } @@ -241,12 +241,12 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { given: 'an existing cm handle with no data producer identifier' DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': oldDataProducerIdentifier]) and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someDataProducerIdentifier') + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'New Data Producer Identifier') when: 'data producer identifier updated' objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) then: 'the update node leaves method is invoked once' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> { args -> - assert args[3].contains('someDataProducerIdentifier') + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> + assert args[3].contains('New Data Producer Identifier') } and: 'correct information is logged' def lastLoggingEvent = logger.list[0] @@ -258,6 +258,17 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { 'blank to something' | '' } + def 'Update CM Handle data producer identifier with same value'() { + given: 'an existing cm handle with no data producer identifier' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': 'same id']) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'same id') + when: 'data producer identifier updated' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is not invoked' + 0 * mockCpsDataService.updateNodeLeaves(*_) + } + def 'Update CM Handle data producer identifier from some data producer identifier to another data producer identifier'() { given: 'an existing cm handle with a data producer identifier' DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': 'someDataProducerIdentifier']) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index fbfbac59e..da3116283 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -19,51 +19,44 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl +package org.onap.cps.ncmp.impl.inventory -import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR - -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.models.UpgradedCmHandles -import com.fasterxml.jackson.databind.ObjectMapper import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.ncmp.api.impl.exception.DmiRequestException -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.SchemaSetNotFoundException -import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification -class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class CmHandleRegistrationServiceSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') def mockCpsModuleService = Mock(CpsModuleService) - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockDmiDataOperations = Mock(DmiDataOperations) - def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler) + def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler) def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) - def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandleQueryService) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) def mockCpsDataService = Mock(CpsDataService) def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>) @@ -71,10 +64,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) - def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations, - mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries, - stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker)) + def objectUnderTest = Spy(new CmHandleRegistrationService( + mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) def setup() { // always accept all cm handles @@ -418,4 +410,46 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } + def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { + given: 'an existing cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, + dataStores: CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(initialDataSyncState) + .build()).build()) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) + then: 'the data sync enabled flag is set to #dataSyncEnabled' + compositeState.dataSyncEnabled == dataSyncEnabledFlag + and: 'the data store sync state is set to #expectedDataStoreSyncState' + compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState + and: 'the cps data service to delete data nodes is invoked the expected number of times' + deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) + and: 'the inventory persistence service to update node leaves is called with the correct values' + saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) + where: 'the following data sync enabled flag is used' + scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations + 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 + 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 + 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 + 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 + } + + def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { + given: 'a cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to true' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) + then: 'the expected exception is thrown' + thrown(CpsException) + and: 'the inventory persistence service to update node leaves is not invoked' + 0 * mockInventoryPersistence.saveCmHandleState(_, _) + } + + + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index 9907e9ab2..d58a1dfa0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -20,24 +20,17 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory - -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.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS +package org.onap.cps.ncmp.impl.inventory import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.ModuleDefinition @@ -46,10 +39,19 @@ import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification + import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NO_TIMESTAMP +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + class InventoryPersistenceImplSpec extends Specification { def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -62,7 +64,7 @@ class InventoryPersistenceImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries) @@ -303,41 +305,6 @@ class InventoryPersistenceImplSpec extends Specification { assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode() } - def 'Find cm handle parent data node using alternate ids'() { - given: 'cm handle in the registry with alternateId /a/b' - def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='/a/b']" - mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] - and: 'no other cm handle' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - expect: 'querying for alternate id a matching result found' - assert objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') != null - where: 'the following parameters are used' - scenario | alternateId - 'exact match' | '/a/b' - 'exact match with trailing separator' | '/a/b/' - 'child match' | '/a/b/c' - } - - def 'Find cm handle parent data node using alternate ids mismatches'() { - given: 'cm handle in the registry with alternateId' - def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='${cpsPath}]" - mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] - and: 'no other cm handle' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - when: 'attempt to find alternateId' - objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') - then: 'no alternate id found exception thrown' - def thrown = thrown(NoAlternateIdParentFoundException) - and: 'the exception has the relevant details from the error response' - assert thrown.message == 'No matching (parent) cm handle found using alternate ids' - assert thrown.details == 'cannot find a datanode with alternate id ' + alternateId - where: 'the following parameters are used' - scenario | alternateId | cpsPath - 'no match for parent only' | '/a' | '/a/b' - 'no match at all' | '/x/y/z' | '/a/b' - 'no match with trailing separator' | '/c/d/' | '/c/d' - } - def 'Attempt to get non existing cm handle data node by alternate id'() { given: 'query service is invoked and returns empty collection of data nodes' mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy new file mode 100644 index 000000000..49d4c39a1 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -0,0 +1,223 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +import java.util.stream.Collectors + +class NetworkCmProxyInventoryFacadeSpec extends Specification { + + def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService) + def mockCmHandleQueryService = Mock(CmHandleQueryService) + def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService) + def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + def mockInventoryPersistence = Mock(InventoryPersistence) + + def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper) + + def 'Update DMI Registration'() { + given: 'an (updated) dmi plugin registration' + def dmiPluginRegistration = Mock(DmiPluginRegistration) + when: 'the registration is submitted ' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the call is delegated to the cm handle registration service' + 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + } + + def 'Execute cm handle id search for inventory'() { + given: 'a ConditionApiProperties object' + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllProperties' + conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] + def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters() + cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties> + and: 'the system returns an set of cmHandle ids' + mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] + when: 'executing the search' + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + then: 'the result returns the correct 2 elements' + assert result.size() == 2 + assert result.contains('cmHandle1') + assert result.contains('cmHandle2') + } + + def 'Get all cm handle IDs by DMI plugin identifier.' () { + given: 'cm handle queries service returns cm handles' + 1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] + when: 'cm handle Ids are requested with dmi plugin identifier' + def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') + then: 'the result size is correct' + assert result.size() == 2 + and: 'the result returns the correct details' + assert result.containsAll('cm-handle-1','cm-handle-2') + } + + def 'Getting Yang Resources.'() { + when: 'yang resources is called' + objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') + then: 'CPS module services is invoked for the correct dataspace and cm handle' + 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') + } + + def 'Get a cm handle.'() { + given: 'the system returns a yang modelled cm handle' + def dmiServiceName = 'some service name' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] + def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] + def moduleSetTag = 'some-module-set-tag' + def alternateId = 'some-alternate-id' + def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, + dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, + moduleSetTag: moduleSetTag, alternateId: alternateId) + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle details for a given cm handle id from ncmp service' + def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') + then: 'the result is a ncmpServiceCmHandle' + result.class == NcmpServiceCmHandle.class + and: 'the cm handle contains the cm handle id' + result.cmHandleId == 'some-cm-handle' + and: 'the cm handle contains the alternate id' + result.alternateId == 'some-alternate-id' + and: 'the cm handle contains the module-set-tag' + result.moduleSetTag == 'some-module-set-tag' + and: 'the cm handle contains the DMI Properties' + result.dmiProperties ==[ Book:'Romance Novel' ] + and: 'the cm handle contains the public Properties' + result.publicProperties == [ "Public Book":'Public Romance Novel' ] + and: 'the cm handle contains the cm handle composite state' + result.compositeState == compositeState + } + + def 'Get cm handle public properties'() { + given: 'a yang modelled cm handle' + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle public properties for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') + then: 'the result returns the correct data' + result == [ 'public prop' : 'some public prop' ] + } + + def 'Get cm handle composite state'() { + given: 'a yang modelled cm handle' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle composite state for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') + then: 'the result returns the correct data' + result == compositeState + } + + def 'Execute cm handle id search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockParameterizedCmHandleQueryService.queryCmHandleIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> ['cm-handle-id-1'] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result == ['cm-handle-id-1'] + } + + def 'Getting module definitions by module'() { + when: 'get module definitions is performed with module name' + objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + then: 'ncmp inventory persistence service is invoked once with correct parameters' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + } + + def 'Getting module definitions by cm handle id'() { + when: 'get module definitions is performed with cm handle id' + objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') + then: 'ncmp inventory persistence service is invoked once with correct parameter' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') + } + + def 'Execute cm handle search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockParameterizedCmHandleQueryService.queryCmHandles( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result.stream().map(cmHandle -> cmHandle.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set + } + + def 'Set Cm Handle Data Sync flag.'() { + when: 'setting data sync enabled flag' + objectUnderTest.setDataSyncEnabled('ch-1',true) + then: 'call is delegated to the cm handle registration service' + mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true) + } + + def dataStores() { + CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) + .lastSyncTime('some-timestamp').build()).build() + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy index 5f4f248c6..d8bb559bf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy @@ -18,16 +18,17 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +package org.onap.cps.ncmp.impl.inventory import org.onap.cps.api.CpsQueryService +import org.onap.cps.ncmp.api.impl.NetworkCmProxyQueryServiceImpl import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + class NetworkCmProxyQueryServiceImplSpec extends Specification { def mockCpsQueryService = Mock(CpsQueryService) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 7c410cc58..dfd549e63 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -18,17 +18,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +package org.onap.cps.ncmp.impl.inventory import org.onap.cps.cpspath.parser.PathParsingException -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueriesImpl -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataInUseException import org.onap.cps.spi.exceptions.DataValidationException @@ -36,16 +31,18 @@ import org.onap.cps.spi.model.ConditionProperties import org.onap.cps.spi.model.DataNode import spock.lang.Specification -class NetworkCmProxyCmHandleQueryServiceSpec extends Specification { +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + +class ParameterizedCmHandleQueryServiceSpec extends Specification { - def cmHandleQueries = Mock(CmHandleQueries) - def partiallyMockedCmHandleQueries = Spy(CmHandleQueries) + def cmHandleQueries = Mock(CmHandleQueryService) + def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService) def mockInventoryPersistence = Mock(InventoryPersistence) def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'])) - def objectUnderTest = new NetworkCmProxyCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) - def objectUnderTestWithPartiallyMockedQueries = new NetworkCmProxyCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) + def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) + def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) def 'Query cm handle ids with cpsPath.'() { given: 'a cmHandleWithCpsPath condition property' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy index 100705ff5..26541de64 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy index 00edb8d0d..51c1f4bb1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.utils +package org.onap.cps.ncmp.impl.inventory.models + import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy index cfc6ffda1..3f379b0b8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy @@ -18,14 +18,12 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.yangmodels +package org.onap.cps.ncmp.impl.inventory.models -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy index 64ecafefc..751c97a4d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy @@ -18,12 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.executor +package org.onap.cps.ncmp.impl.inventory.sync + -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification + import java.util.concurrent.TimeoutException import java.util.function.Supplier diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy index 0ffb56791..2ee5226dc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy @@ -18,19 +18,19 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + class DataSyncWatchdogSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 44bc18200..52b48a366 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -19,40 +19,40 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED +package org.onap.cps.ncmp.impl.inventory.sync import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.AnnotationConfigApplicationContext import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.onap.cps.utils.JsonObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import spock.lang.Specification + import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.stream.Collectors +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + class ModuleOperationsUtilsSpec extends Specification{ - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockDmiDataOperations = Mock(DmiDataOperations) @@ -202,7 +202,7 @@ class ModuleOperationsUtilsSpec extends Specification{ def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) - mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName, 'cm-handle-123', _) >> responseEntity + mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity when: 'get resource data is called' def result = objectUnderTest.getResourceData('cm-handle-123') then: 'the returned data is correct' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy index cb933fafb..685a65b15 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -18,35 +18,34 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync +package org.onap.cps.ncmp.impl.inventory.sync import org.onap.cps.api.CpsAnchorService - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE - -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.spi.model.DataNode import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.exceptions.SchemaSetNotFoundException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE + class ModuleSyncServiceSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockDmiModelOperations = Mock(DmiModelOperations) def mockCpsAnchorService = Mock(CpsAnchorService) - def mockCmHandleQueries = Mock(CmHandleQueries) + def mockCmHandleQueries = Mock(CmHandleQueryService) def mockCpsDataService = Mock(CpsDataService) def mockJsonObjectMapper = Mock(JsonObjectMapper) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 6c0d6dfb8..a9dbf07e2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -19,10 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED +package org.onap.cps.ncmp.impl.inventory.sync import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger @@ -32,17 +29,21 @@ import com.hazelcast.config.Config import com.hazelcast.instance.impl.HazelcastInstanceFactory import com.hazelcast.map.IMap import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory import spock.lang.Specification + import java.util.concurrent.atomic.AtomicInteger +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + class ModuleSyncTasksSpec extends Specification { def logger = Spy(ListAppender<ILoggingEvent>) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 1752a17a1..155edc8bc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -19,15 +19,15 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor -import java.util.concurrent.ArrayBlockingQueue +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.model.DataNode import spock.lang.Specification +import java.util.concurrent.ArrayBlockingQueue + class ModuleSyncWatchdogSpec extends Specification { def mockSyncUtils = Mock(ModuleOperationsUtils) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy index 6d09df0d4..8e59922c4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.config.embeddedcache +package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.config.Config import com.hazelcast.core.Hazelcast @@ -29,6 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import spock.util.concurrent.PollingConditions + import java.util.concurrent.BlockingQueue import java.util.concurrent.TimeUnit diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy index f37d5a41e..a943962e9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy @@ -18,7 +18,8 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.inventory.sync.config +package org.onap.cps.ncmp.impl.inventory.sync + import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy new file mode 100644 index 000000000..ad8449582 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.utils + +import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class AlternateIdMatcherSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence) + + def setup() { + given: 'cm handle in the registry with alternate id /a/b' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId('/a/b') >> new DataNode() + and: 'no other cm handle' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId(_) >> { throw new DataNodeNotFoundException('', '') } + } + + def 'Finding longest alternate id matches.'() { + expect: 'querying for alternate id a matching result found' + assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null + where: 'the following parameters are used' + scenario | targetAlternateId + 'exact match' | '/a/b' + 'parent match' | '/a/b/c' + 'grand parent match' | '/a/b/c/d' + 'trailing separator match' | '/a/b/' + } + + def 'Attempt to find longest alternate id match without any matches.'() { + when: 'attempt to find alternateId' + objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') + then: 'no alternate id match found exception thrown' + def thrown = thrown(NoAlternateIdMatchFoundException) + and: 'the exception has the relevant details from the error response' + assert thrown.message == 'No matching cm handle found using alternate ids' + assert thrown.details == 'cannot find a datanode with alternate id ' + targetAlternateId + where: 'the following parameters are used' + scenario | targetAlternateId + 'no match for parent only' | '/a' + 'no match for other child' | '/a/c' + 'no match at all' | '/x/y' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy index b0be29d93..162a9831c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy @@ -32,7 +32,7 @@ import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.springframework.boot.SpringApplication import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification @@ -64,18 +64,18 @@ class AbstractModelLoaderSpec extends Specification { applicationContext.close() } - def 'Application ready event'() { - when: 'Application (ready) event is triggered' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + def 'Application started event'() { + when: 'Application (started) event is triggered' + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the onboard/upgrade method is executed' 1 * objectUnderTest.onboardOrUpgradeModel() } - def 'Application ready event with start up exception'() { + def 'Application started event with start up exception'() { given: 'a start up exception is thrown doing model onboarding' objectUnderTest.onboardOrUpgradeModel() >> { throw new NcmpStartUpException('test message','details are not logged') } - when: 'Application (ready) event is triggered' - objectUnderTest.onApplicationEvent(new ApplicationReadyEvent(new SpringApplication(), null, applicationContext, null)) + when: 'Application (started) event is triggered' + objectUnderTest.onApplicationEvent(new ApplicationStartedEvent(new SpringApplication(), null, applicationContext, null)) then: 'the exception message is logged' def logs = loggingListAppender.list.toString() assert logs.contains('test message') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index f3b405b11..dd1fd0b46 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -31,11 +31,11 @@ import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.model.Dataspace import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME class CmDataSubscriptionModelLoaderSpec extends Specification { @@ -65,11 +65,11 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { applicationContext.close() } - def 'Onboard subscription model via application ready event.'() { + def 'Onboard subscription model via application started event.'() { given: 'dataspace is ready for use' mockCpsDataspaceService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') when: 'the application is ready' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the module service to create schema set is called once' 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', expectedYangResourcesToContentMap) and: 'the admin service to create an anchor set is called once' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index cd659bb52..76c1589be 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -20,23 +20,22 @@ package org.onap.cps.ncmp.init -import org.onap.cps.api.CpsAnchorService - -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 ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.Dataspace import org.slf4j.LoggerFactory -import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR + class InventoryModelLoaderSpec extends Specification { def mockCpsAdminService = Mock(CpsDataspaceService) @@ -68,8 +67,8 @@ class InventoryModelLoaderSpec extends Specification { def 'Onboard subscription model via application ready event.'() { given: 'dataspace is ready for use' mockCpsAdminService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') - when: 'the application is ready' - objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) + when: 'the application is started' + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the module service is used to create the new schema set from the correct resource' 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'dmi-registry-2024-02-23', expectedYangResourceToContentMap) and: 'the admin service is used to update the anchor' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/TopicValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/TopicValidatorSpec.groovy new file mode 100644 index 000000000..f96502984 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/TopicValidatorSpec.groovy @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.utils + +import org.onap.cps.ncmp.exceptions.InvalidTopicException +import org.onap.cps.ncmp.utils.TopicValidator +import spock.lang.Specification + +class TopicValidatorSpec extends Specification { + + def 'Valid topic name validation.'() { + when: 'a valid topic name is validated' + TopicValidator.validateTopicName('my-valid-topic') + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'Validating invalid topic names.'() { + when: 'the invalid topic name is validated' + TopicValidator.validateTopicName(topicName) + then: 'boolean response will be returned for #scenario' + thrown(InvalidTopicException) + where: 'the following names are used' + scenario | topicName + 'empty topic' | '' + 'blank topic' | ' ' + 'invalid non empty topic' | '1_5_*_#' + } +} diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index 574b49982..5b10e7376 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2023 Nordix Foundation +# Copyright (C) 2021-2024 Nordix Foundation # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,14 +30,21 @@ app: async-m2m: topic: ncmp-async-m2m avc: - subscription-topic: subscription + cm-subscription-ncmp-in: subscription cm-events-topic: cm-events - subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-} + cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription} ncmp: dmi: httpclient: - connectionTimeoutInSeconds: 180 + data-services: + pendingAcquireMaxCount: 22 + connectionTimeoutInSeconds: 123 + maximumInMemorySizeInMegabytes: 7 + model-services: + pendingAcquireMaxCount: 44 + connectionTimeoutInSeconds: 456 + maximumInMemorySizeInMegabytes: 8 auth: username: some-user password: some-password diff --git a/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json b/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json index 6b665495c..04d37b8bb 100644 --- a/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json +++ b/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json @@ -6,14 +6,14 @@ "targetFilter": ["ch1","ch2"], "scopeFilter": { "datastore": "ncmp-datastore:passthrough-operational", - "xpath-filter": ["/x1/y1","x2/y2"] + "xpathFilter": ["/x1/y1","x2/y2"] } }, { "targetFilter": ["ch3","ch4"], "scopeFilter": { "datastore": "ncmp-datastore:passthrough-operational", - "xpath-filter": ["/x3/y3","x4/y4"] + "xpathFilter": ["/x3/y3","x4/y4"] } } ] |