diff options
author | bmiklos <miklos.baranyak@est.tech> | 2022-08-25 18:28:16 +0200 |
---|---|---|
committer | bmiklos <miklos.baranyak@est.tech> | 2022-09-01 16:56:05 +0200 |
commit | bbaf501627a69707bd797c535750996a9dd205aa (patch) | |
tree | 94e46ff4e6657bcda6b312947567e5fee7494b03 /cps-ncmp-rest/src | |
parent | 33236ba508ca3536dbacce944b19f880aa6ff944 (diff) |
Implement merging all ncmp datastore endpoints into one
- Merging all endpoints
under /v1/ch/{cm-handle}/data/ds/ncmp-datastore:*
to /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}
- Implementing missing tests from parent
- Introducing abstract class to keep the common code and just pass in
the supplier to be executed in sync or async manner
- Removed the existing get endpoints for passthrough-running,
passthrough-operational and operational and merged them into a common
get endpoint
Issue-ID: CPS-1178
Issue-ID: CPS-1001
Change-Id: I6956c81d5acfa8fb11217bcc16cb795b62070fa3
Signed-off-by: bmiklos <miklos.baranyak@est.tech>
Diffstat (limited to 'cps-ncmp-rest/src')
14 files changed, 754 insertions, 217 deletions
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index d2ed393794..9aa8263fc5 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -29,21 +29,18 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandler; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory; import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; @@ -53,9 +50,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; import org.onap.cps.ncmp.rest.util.DeprecationHelper; -import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -68,94 +63,50 @@ import org.springframework.web.bind.annotation.RestController; public class NetworkCmProxyController implements NetworkCmProxyApi { private static final String NO_BODY = null; - private static final String NO_REQUEST_ID = null; - private static final String NO_TOPIC = null; private final NetworkCmProxyDataService networkCmProxyDataService; private final JsonObjectMapper jsonObjectMapper; - private final DeprecationHelper deprecationHelper; private final NcmpRestInputMapper ncmpRestInputMapper; private final CmHandleStateMapper cmHandleStateMapper; - private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; - @Value("${notification.async.executor.time-out-value-in-ms:2000}") - private int timeOutInMilliSeconds; - @Value("${notification.enabled:true}") - private boolean asyncEnabled; + private final NcmpDatastoreResourceRequestHandlerFactory ncmpDatastoreResourceRequestHandlerFactory; /** - * Get resource data from operational datastore. + * Get resource data from datastore. * - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter + * @param topicParamInQuery topic query parameter + * @param includeDescendants whether include descendants * @return {@code ResponseEntity} response from dmi plugin */ - @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-operational request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-operational." - + " Will use synchronous operation."); - } - final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); - - return ResponseEntity.ok(responseObject); - } - - /** - * Get resource data from pass-through running datastore. - * - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @return {@code ResponseEntity} response from dmi plugin - */ @Override - public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-running request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-running." - + " Will use synchronous operation."); - } + public ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { - final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); + final NcmpDatastoreResourceRequestHandler ncmpDatastoreResourceRequestHandler = + ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.fromDatastoreName(datastoreName)); - return ResponseEntity.ok(responseObject); + return ncmpDatastoreResourceRequestHandler.getResourceData(cmHandle, resourceIdentifier, + optionsParamInQuery, topicParamInQuery, includeDescendants); } @Override public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, - final Object requestBody, final String contentType) { - final Object responseObject = networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, PATCH, jsonObjectMapper.asJsonString(requestBody), contentType); + final String cmHandle, + final Object requestBody, + final String contentType) { + final Object responseObject = networkCmProxyDataService + .writeResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, PATCH, + jsonObjectMapper.asJsonString(requestBody), contentType); return ResponseEntity.ok(responseObject); } @@ -163,14 +114,16 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Create resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of body + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of body * @return {@code ResponseEntity} response from dmi plugin */ @Override public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, final Object requestBody, final String contentType) { + final String cmHandle, + final Object requestBody, + final String contentType) { networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType); return new ResponseEntity<>(HttpStatus.CREATED); @@ -180,9 +133,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Update resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of the body + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of the body * @return response entity */ @Override @@ -197,11 +150,11 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** - * Delete resource data in datastore pass-through running for a given cm-handle. + * Delete resource data in datastore pass-through running for a given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param contentType content type of the body + * @param cmHandle cm handle identifier + * @param contentType content type of the body * @return response entity no content if request is successful */ @Override @@ -209,7 +162,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String resourceIdentifier, final String contentType) { networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, DELETE, NO_BODY, contentType); + resourceIdentifier, DELETE, NO_BODY, contentType); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -240,7 +193,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); final Set<String> cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters); @@ -249,6 +202,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Search for Cm Handle and Properties by Name. + * * @param cmHandleId cm-handle identifier * @return cm handle and its properties */ @@ -261,33 +215,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Get Cm Handle Properties by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle properties */ @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); cmHandlePublicProperties.add(networkCmProxyDataService.getCmHandlePublicProperties(cmHandleId)); final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties = - new RestOutputCmHandlePublicProperties(); + new RestOutputCmHandlePublicProperties(); restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties); return ResponseEntity.ok(restOutputCmHandlePublicProperties); } /** * Get Cm Handle State by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle state */ @Override public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CompositeState cmHandleState = networkCmProxyDataService.getCmHandleCompositeState(cmHandleId); final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState = - new RestOutputCmHandleCompositeState(); + new RestOutputCmHandleCompositeState(); restOutputCmHandleCompositeState.setState( - cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); + cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); return ResponseEntity.ok(restOutputCmHandleCompositeState); } @@ -314,22 +270,22 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) { final List<RestModuleReference> restModuleReferences = - networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() - .map(ncmpRestInputMapper::toRestModuleReference) - .collect(Collectors.toList()); + networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() + .map(ncmpRestInputMapper::toRestModuleReference) + .collect(Collectors.toList()); return new ResponseEntity<>(restModuleReferences, HttpStatus.OK); } /** * Set the data sync enabled flag, along with the data sync state for the specified cm handle. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param dataSyncEnabledFlag data sync enabled flag * @return response entity ok if request is successful */ @Override public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabledFlag) { + final Boolean dataSyncEnabledFlag) { networkCmProxyDataService.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag); return new ResponseEntity<>(HttpStatus.OK); } @@ -345,15 +301,6 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { return restOutputCmHandle; } - private static boolean isValidTopic(final String topicName) { - if (topicName == null) { - return false; - } - if (CpsValidator.validateTopicName(topicName)) { - return true; - } - throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); - } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java new file mode 100644 index 0000000000..959c85d141 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java @@ -0,0 +1,53 @@ +/* + * ============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.rest.controller.handlers; + + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; + +@Getter +public enum DatastoreType { + + OPERATIONAL("ncmp-datastore:operational"), + PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"), + PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"); + + DatastoreType(final String datastoreName) { + this.datastoreName = datastoreName; + } + + private final String datastoreName; + private static final Map<String, DatastoreType> datastoreNameToDatastoreType = new HashMap<>(); + + static { + Arrays.stream(DatastoreType.values()).forEach( + type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type)); + } + + public static DatastoreType fromDatastoreName(final String datastoreName) { + return datastoreNameToDatastoreType.get(datastoreName); + } + +} + diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..6ed9b8c4d1 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java @@ -0,0 +1,55 @@ +/* + * ============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.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.spi.FetchDescendantsOption; + +@Slf4j +public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + final FetchDescendantsOption fetchDescendantsOption = + Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + : FetchDescendantsOption.OMIT_DESCENDANTS; + + return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier, + fetchDescendantsOption); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..196e5bd33d --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java @@ -0,0 +1,51 @@ +/* + * ============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.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughOperationalResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java new file mode 100644 index 0000000000..5bf16b7499 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java @@ -0,0 +1,50 @@ +/* + * ============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.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughRunningResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java new file mode 100644 index 0000000000..a6d313b05f --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java @@ -0,0 +1,92 @@ +/* + * ============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.rest.controller.handlers; + +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.util.TopicValidator; +import org.springframework.http.ResponseEntity; + +@RequiredArgsConstructor +@Slf4j +public abstract class NcmpDatastoreResourceRequestHandler { + + private static final String NO_REQUEST_ID = null; + private static final String NO_TOPIC = null; + + protected final NetworkCmProxyDataService networkCmProxyDataService; + protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + protected final int timeOutInMilliSeconds; + protected final boolean notificationFeatureEnabled; + + protected abstract Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant); + + + /** + * Get resource data from datastore. + * + * @param cmHandleId the cm handle + * @param resourceIdentifier the resource identifier + * @param optionsParamInQuery the options param in query + * @param topicParamInQuery the topic param in query + * @param includeDescendants whether include descendants + * @return the response entity + */ + public ResponseEntity<Object> getResourceData(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + + final String requestId = UUID.randomUUID().toString(); + final boolean asyncResponseRequested = topicParamInQuery != null; + + if (asyncResponseRequested && notificationFeatureEnabled) { + TopicValidator.validateTopicName(topicParamInQuery); + log.debug("Received Async request with id {}", requestId); + cpsNcmpTaskExecutor.executeTask( + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, + includeDescendants), timeOutInMilliSeconds); + + return ResponseEntity.ok(Map.of("requestId", requestId)); + } + + if (asyncResponseRequested) { + log.warn("Asynchronous messaging is currently disabled, will use synchronous operation."); + } + + final Object responseObject = + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, + includeDescendants).get(); + + return ResponseEntity.ok(responseObject); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java new file mode 100644 index 0000000000..35bd578ce2 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java @@ -0,0 +1,64 @@ +/* + * ============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.rest.controller.handlers; + +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NcmpDatastoreResourceRequestHandlerFactory { + + private final NetworkCmProxyDataService networkCmProxyDataService; + private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + + @Value("${notification.async.executor.time-out-value-in-ms:2000}") + private int timeOutInMilliSeconds; + @Value("${notification.enabled:true}") + private boolean notificationFeatureEnabled; + + /** + * Gets ncmp datastore handler. + * + * @param datastoreType the datastore type + * @return the ncmp datastore handler + */ + public NcmpDatastoreResourceRequestHandler getNcmpDatastoreResourceRequestHandler( + final DatastoreType datastoreType) { + + switch (datastoreType) { + case OPERATIONAL: + return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_RUNNING: + return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_OPERATIONAL: + return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + default: + return null; + } + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java new file mode 100644 index 0000000000..6a52d5861e --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/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.rest.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-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java index c72373344d..98d7f6fd1d 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java @@ -25,7 +25,6 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.exception.DmiRequestException; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.impl.exception.NcmpException; import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException; import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController; diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java new file mode 100644 index 0000000000..313e7bc012 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java @@ -0,0 +1,44 @@ +/* + * ============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.rest.util; + +import java.util.regex.Pattern; +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException; + +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-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index d568a5a925..6e461fa59e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -23,51 +23,59 @@ package org.onap.cps.ncmp.rest.controller +import com.fasterxml.jackson.databind.ObjectMapper import org.mapstruct.factory.Mappers +import org.onap.cps.TestUtils +import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState -import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.ModuleDefinition +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc 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.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.TestUtils -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.ncmp.api.NetworkCmProxyDataService -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import spock.lang.Specification +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete @WebMvcTest(NetworkCmProxyController) class NetworkCmProxyControllerSpec extends Specification { + public static final int TIMEOUT_IN_MS = 2000 + public static final boolean NOTIFICATION_ENABLED = true + @Autowired MockMvc mvc @@ -92,6 +100,9 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub() + @Value('${rest.api.ncmp-base-path}/v1') def ncmpBasePathV1 @@ -104,21 +115,38 @@ class NetworkCmProxyControllerSpec extends Specification { def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + void setup() { + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.OPERATIONAL) >> + new NcmpDatastoreOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_OPERATIONAL) >> + new NcmpDatastorePassthroughOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_RUNNING) >> + new NcmpDatastorePassthroughRunningResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + } + def 'Get Resource Data from pass-through operational.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)" + "?resourceIdentifier=parent/child&options=(a=1,b=2)" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle' 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', - 'parent/child', - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) + 'parent/child', + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) and: 'response status is Ok' response.status == HttpStatus.OK.value() } @@ -126,22 +154,22 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from #datastoreInUrl with #scenario.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" + "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response + get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response then: 'task executor is called appropriate number of times' - expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, 2000) + expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS) and: 'response status is expected' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions - 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 - 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 + scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions + 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 + 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 } def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() { @@ -168,17 +196,17 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" + "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" and: 'ncmp service returns json object' mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle', - resourceIdentifier, - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) >> '{valid-json}' + resourceIdentifier, + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) >> '{valid-json}' when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'response status is Ok' response.status == HttpStatus.OK.value() @@ -194,7 +222,7 @@ class NetworkCmProxyControllerSpec extends Specification { '? needs to be encoded as %3F' | 'idWith%3F' } - def 'Update resource data from pass-through running.' () { + def 'Update resource data from pass-through running.'() { given: 'update resource data url' def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + "?resourceIdentifier=parent/child" @@ -210,15 +238,15 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.OK.value() } - def 'Create Resource Data from pass-through running with #scenario.' () { + def 'Create Resource Data from pass-through running with #scenario.'() { given: 'resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" def requestBody = '{"some-key":"some-value"}' when: 'create resource request is performed' def response = mvc.perform( - post(url) - .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + post(url) + .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) ).andReturn().response then: 'ncmp service method to create resource called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', @@ -227,14 +255,14 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.CREATED.value() } - def 'Get module references for the given dataspace and cm handle.' () { + def 'Get module references for the given dataspace and cm handle.'() { given: 'get module references url' def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules" when: 'get module resource request is performed' - def response =mvc.perform(get(getUrl)).andReturn().response + def response = mvc.perform(get(getUrl)).andReturn().response then: 'ncmp service method to get yang resource module references is called' mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle') - >> [new ModuleReference(moduleName: 'some-name1',revision: '2021-10-03')] + >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')] and: 'response contains an array with the module name and revision' response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]' and: 'response returns an OK http code' @@ -248,15 +276,15 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandle1 = new NcmpServiceCmHandle() cmHandle1.cmHandleId = 'some-cmhandle-id1' - cmHandle1.publicProperties = [color:'yellow'] + cmHandle1.publicProperties = [color: 'yellow'] def cmHandle2 = new NcmpServiceCmHandle() cmHandle2.cmHandleId = 'some-cmhandle-id2' - cmHandle2.publicProperties = [color:'green'] + cmHandle2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content(jsonString)).andReturn().response + .contentType(MediaType.APPLICATION_JSON) + .content(jsonString)).andReturn().response then: 'response status returns OK' response.status == HttpStatus.OK.value() and: 'the expected response content is returned' @@ -268,14 +296,15 @@ class NetworkCmProxyControllerSpec extends Specification { def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle" and: 'an existing ncmp service cm handle' def cmHandleId = 'some-cm-handle' - def dmiProperties = [ prop:'some DMI property' ] - def publicProperties = [ "public prop":'some public property' ] + def dmiProperties = [prop: 'some DMI property'] + def publicProperties = ["public prop": 'some public property'] def compositeState = compositeStateTestObject() def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) and: 'the service method is invoked with the cm handle id' 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' - def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandleDetailsEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' @@ -286,30 +315,34 @@ class NetworkCmProxyControllerSpec extends Specification { !response.contentAsString.contains("some DMI property") } - def 'Get Cm Handle public properties by Cm Handle id.' () { + def 'Get Cm Handle public properties by Cm Handle id.'() { given: 'a cm handle properties endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties" and: 'some cm handle public properties' - def publicProperties = [ 'public prop':'some public property' ] + def publicProperties = ['public prop': 'some public property'] and: 'the service method is invoked with the cm handle id returning the cm handle public properties' - 1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties + 1 * mockNetworkCmProxyDataService + .getCmHandlePublicProperties('some-cm-handle') >> publicProperties when: 'the cm handle properties api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' assertContainsPublicProperties(response) } - def 'Get Cm Handle composite state by Cm Handle id.' () { + def 'Get Cm Handle composite state by Cm Handle id.'() { given: 'a cm handle state endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state" and: 'some cm handle composite state' def compositeState = compositeStateTestObject() and: 'the service method is invoked with the cm handle id returning the cm handle composite state' - 1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState + 1 * mockNetworkCmProxyDataService + .getCmHandleCompositeState('some-cm-handle') >> compositeState when: 'the cm handle state api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the cm handle state' @@ -323,13 +356,14 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandel1 = new NcmpServiceCmHandle() cmHandel1.cmHandleId = 'some-cmhandle-id1' - cmHandel1.publicProperties = [color:'yellow'] + cmHandel1.publicProperties = [color: 'yellow'] def cmHandel2 = new NcmpServiceCmHandle() cmHandel2.cmHandleId = 'some-cmhandle-id2' - cmHandel2.publicProperties = [color:'green'] + cmHandel2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(jsonString)).andReturn().response then: 'an empty cm handle identifier is returned' @@ -342,9 +376,10 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns cm handle ids' 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2'] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content('{}')).andReturn().response + def response = mvc.perform( + post(searchesEndpoint) + .contentType(MediaType.APPLICATION_JSON) + .content('{}')).andReturn().response then: 'cm handle ids are returned' response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]' } @@ -353,37 +388,39 @@ class NetworkCmProxyControllerSpec extends Specification { when: 'the searches api is invoked' def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" def invalidInputData = '{invalidJson}' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(invalidInputData)).andReturn().response then: 'BAD_REQUEST is returned' response.getStatus() == 400 } - def 'Patch resource data in pass-through running datastore.' () { + def 'Patch resource data in pass-through running datastore.'() { given: 'patch resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'patch data resource request is performed' def response = mvc.perform( - patch(url) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON).content(requestBody) + patch(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content(requestBody) ).andReturn().response then: 'ncmp service method to update resource is called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') + 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') and: 'the response status is OK' response.status == HttpStatus.OK.value() } - def 'Delete resource data in pass-through running datastore.' () { + def 'Delete resource data in pass-through running datastore.'() { given: 'delete resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'delete data resource request is performed' def response = mvc.perform( - delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response + delete(url) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response then: 'the ncmp service method to delete resource is called (with null as body)' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8') @@ -394,12 +431,12 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" + "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON_VALUE) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON_VALUE) ).andReturn().response then: 'async request id is generated' assert response.contentAsString.contains("requestId") @@ -409,32 +446,68 @@ class NetworkCmProxyControllerSpec extends Specification { ':passthrough-running' | 'passthrough-running' } - def 'Get module definitions based on cmHandleId.' () { + def 'Get module definitions based on cmHandleId.'() { when: 'get module definition request is performed' - def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) - .andReturn().response + def response = mvc.perform( + get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) + .andReturn().response then: 'ncmp service method to get module definitions is called' mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') - >> [new ModuleDefinition('sampleModuleName', '2021-10-03', - 'module sampleModuleName{ sample module content }')] + >> [new ModuleDefinition('sampleModuleName', '2021-10-03', + 'module sampleModuleName{ sample module content }')] and: 'response contains an array with the module name, revision and content' response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]' and: 'response returns an OK http code' response.status == HttpStatus.OK.value() } - def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario' () { + def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() { when: 'the set data sync enabled request is invoked' - def response = mvc.perform(put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) - .andReturn().response + def response = mvc.perform( + put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) + .andReturn().response then: 'method to set data sync enabled is called' 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) and: 'the response returns an OK http code' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | dataSyncEnabledFlag - 'enabled' | true - 'disabled' | false + scenario | dataSyncEnabledFlag + 'enabled' | true + 'disabled' | false + } + + def 'Get Resource Data from operational without descendants.'() { + given: 'resource data url' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&include-descendants=false" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperational' + 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', + 'parent/child', + FetchDescendantsOption.OMIT_DESCENDANTS) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() + } + + def 'Get Resource Data from operational including descendants.'() { + given: 'resource data url' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&include-descendants=true" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperational' + 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', + 'parent/child', + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() } def dataStores() { @@ -453,7 +526,7 @@ class NetworkCmProxyControllerSpec extends Specification { } def assertContainsAll(response, assertContent) { - assertContent.forEach( string -> { assert(response.contentAsString.contains(string)) }) + assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) }) return void } @@ -476,8 +549,8 @@ class NetworkCmProxyControllerSpec extends Specification { def assertContainsPublicProperties(response) { def expectedContent = [ - '"publicCmHandleProperties":' , - '"public prop"' , + '"publicCmHandleProperties":', + '"public prop"', '"some public property"' ] return assertContainsAll(response, expectedContent) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy new file mode 100644 index 0000000000..3f7a8a5ce2 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy @@ -0,0 +1,20 @@ +package org.onap.cps.ncmp.rest.controller.handlers + +import spock.lang.Specification + +class NcmpDatastoreResourceRequestHandlerFactorySpec extends Specification { + + def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null) + + def 'Creating ncmp datastore request handlers.'() { + when: 'a ncmp datastore request handler is created for #datastoreType' + def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType) + then: 'the result is of the expected class' + result.class == expectedClass + where: 'the following type of datastore is used' + datastoreType || expectedClass + DatastoreType.OPERATIONAL || NcmpDatastoreOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_RUNNING || NcmpDatastorePassthroughRunningResourceRequestHandler + } +}
\ No newline at end of file diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy index ce908e7547..b8b7fe3bc6 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -29,8 +29,9 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException @@ -48,9 +49,7 @@ import spock.lang.Specification import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY -import static org.springframework.http.HttpStatus.BAD_REQUEST -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR -import static org.springframework.http.HttpStatus.NOT_FOUND +import static org.springframework.http.HttpStatus.* import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -78,6 +77,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory mockedNcmpDatastoreResourceRequestHandlerFactory = Mock() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp @@ -125,7 +127,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { def 'Failing DMI Request - passthrough scenario'() { given: 'failing DMI request' - setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400) , NCMP) + setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP) when: 'the DMI request is executed' def response = performTestRequest(NCMP) then: 'NCMP service responds with 502 Bad Gateway status' @@ -150,7 +152,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { return mvc.perform(post("$dataNodeBaseEndpointNcmpInventory/ch").contentType(MediaType.APPLICATION_JSON).content(jsonData)).andReturn().response } - static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) { + static void assertTestResponse(response, expectedStatus, expectedErrorMessage, expectedErrorDetails) { assert response.status == expectedStatus.value() def content = new JsonSlurper().parseText(response.contentAsString) assert content['status'].toString().contains(expectedStatus.toString()) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy new file mode 100644 index 0000000000..e626e1505d --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/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.rest.util + +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException +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_*_#' + } + +} |