From 34c424689a52614fb414d65899282497fe25b164 Mon Sep 17 00:00:00 2001 From: Serge Simard Date: Thu, 8 Aug 2019 10:55:57 -0400 Subject: Resource Configuration Snapshots Executor and API Issue-ID: CCSDK-1604 Signed-off-by: Serge Simard Change-Id: I349c649e941431b48a309123489d26fb22e0e50a Signed-off-by: Serge Simard --- .../component-config-snapshots-executor.json | 90 +++++ ms/blueprintsprocessor/application/pom.xml | 9 +- .../cds/blueprintsprocessor/SwaggerConfig.java | 4 +- .../functions/config-snapshots/pom.xml | 58 ++++ .../snapshots/ComponentConfigSnapshotsExecutor.kt | 262 +++++++++++++++ .../config/snapshots/db/ResourceConfigSnapshot.kt | 85 +++++ .../db/ResourceConfigSnapshotRepository.kt | 39 +++ .../snapshots/db/ResourceConfigSnapshotService.kt | 77 +++++ .../ComponentConfigSnapshotsExecutorTest.kt | 365 +++++++++++++++++++++ .../db/ResourceConfigSnapshotServiceTest.kt | 94 ++++++ .../src/test/resources/application-test.properties | 32 ++ .../src/test/resources/keystore.p12 | Bin 0 -> 2588 bytes .../src/test/resources/logback-test.xml | 35 ++ .../payload/requests/config-payload-candidate.json | 39 +++ .../payload/requests/config-payload-running.json | 35 ++ .../payload/requests/interface-candidate.xml | 34 ++ .../payload/requests/interface-running.xml | 32 ++ ms/blueprintsprocessor/functions/pom.xml | 1 + .../modules/inbounds/configs-api/pom.xml | 50 +++ .../api/ResourceConfigSnapshotController.kt | 124 +++++++ .../configs/api/ResourceConfigSnapshotException.kt | 20 ++ .../api/ResourceConfigSnapshotExceptionHandler.kt | 118 +++++++ .../api/ResourceConfigSnapshotControllerTest.kt | 175 ++++++++++ .../src/test/resources/application-test.properties | 30 ++ .../configs-api/src/test/resources/logback.xml | 35 ++ ms/blueprintsprocessor/modules/inbounds/pom.xml | 1 + ms/blueprintsprocessor/parent/pom.xml | 24 ++ 27 files changed, 1865 insertions(+), 3 deletions(-) create mode 100644 components/model-catalog/definition-type/starter-type/node_type/component-config-snapshots-executor.json create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/pom.xml create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutor.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshot.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotRepository.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotService.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutorTest.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotServiceTest.kt create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/application-test.properties create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/keystore.p12 create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/logback-test.xml create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-candidate.json create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-running.json create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-candidate.xml create mode 100644 ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-running.xml create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/pom.xml create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotController.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotException.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotExceptionHandler.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotControllerTest.kt create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/application-test.properties create mode 100644 ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/logback.xml diff --git a/components/model-catalog/definition-type/starter-type/node_type/component-config-snapshots-executor.json b/components/model-catalog/definition-type/starter-type/node_type/component-config-snapshots-executor.json new file mode 100644 index 000000000..1cc366637 --- /dev/null +++ b/components/model-catalog/definition-type/starter-type/node_type/component-config-snapshots-executor.json @@ -0,0 +1,90 @@ +{ + "description": "This is Resource configuration snapshots Execution Component.", + "version": "1.0.0", + "attributes": { + "config-snapshot-status": { + "required": true, + "type": "string" + }, + "config-snapshot-message": { + "required": true, + "type": "string" + }, + "config-snapshot-value": { + "required": false, + "type": "string" + } + }, + "capabilities": { + "component-node": { + "type": "tosca.capabilities.Node" + } + }, + "interfaces": { + "ComponentConfigSnapshotsExecutor": { + "operations": { + "process": { + "inputs": { + "operation": { + "description": "Operation to perform: fetch, store, diff. (required)", + "required": true, + "type": "string", + "constraints": [ + { + "valid_values": [ + "fetch", + "store", + "diff" + ] + } + ] + }, + "resource-id": { + "description": "Identifier for the resource config to operate on. (required)", + "required": true, + "type": "string" + }, + "resource-type": { + "description": "Type of the resource config to operate on, e.g. PNF, VNF, etc. (required)", + "required": true, + "type": "string" + }, + "resource-status" : { + "description": "Status of the resource to fetch or store, either RUNNING or CANDIDATE. (optional)", + "required" : false, + "type" : "string", + "default": "RUNNING", + "constraints": [ + { + "valid_values": [ + "RUNNING", + "CANDIDATE" + ] + } + ] + }, + "resource-snapshot": { + "description": "The resource config snapshot to store for the resource identified by id/type/status. (store operation only)", + "required": false, + "type": "string" + }, + "diff-content-type": { + "description": "Specify the type of content expected, to perform comparison on. (diff operation only)", + "required": false, + "type": "string", + "constraints": [ + { + "valid_values": [ + "xml", + "json" + ] + } + ] + } + } + } + } + } + }, + "derived_from": "tosca.nodes.Component" +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/application/pom.xml b/ms/blueprintsprocessor/application/pom.xml index a504ce3cc..7ee52a9b4 100755 --- a/ms/blueprintsprocessor/application/pom.xml +++ b/ms/blueprintsprocessor/application/pom.xml @@ -66,6 +66,10 @@ org.onap.ccsdk.cds.blueprintsprocessor selfservice-api + + org.onap.ccsdk.cds.blueprintsprocessor + configs-api + @@ -88,7 +92,10 @@ org.onap.ccsdk.cds.blueprintsprocessor.functions cli-executor - + + org.onap.ccsdk.cds.blueprintsprocessor.functions + config-snapshots + com.h2database h2 diff --git a/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.java b/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.java index 4df55ffde..82693c4fd 100644 --- a/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.java +++ b/ms/blueprintsprocessor/application/src/main/java/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.java @@ -52,8 +52,8 @@ public class SwaggerConfig { private ApiInfo apiInfo() { return new ApiInfo( "CDS Blueprints Processor APIs", - "Provide APIs to interact with CBA, their related resolved resources and templates.", - "0.5.0", + "Provide APIs to interact with CBA, their resolved resources and templates, and stored resource configurations.", + "0.5.1", null, new Contact("CCSDK Team", "www.onap.org", "onap-discuss@lists.onap.org"), "Apache 2.0", diff --git a/ms/blueprintsprocessor/functions/config-snapshots/pom.xml b/ms/blueprintsprocessor/functions/config-snapshots/pom.xml new file mode 100644 index 000000000..7963aa339 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/pom.xml @@ -0,0 +1,58 @@ + + + + + functions + org.onap.ccsdk.cds.blueprintsprocessor + 0.5.2-SNAPSHOT + + 4.0.0 + + org.onap.ccsdk.cds.blueprintsprocessor.functions + config-snapshots + Blueprints Processor Function - Config Snapshots + Blueprints Processor Function - Config Snapshots + + + + org.python + jython-standalone + + + com.h2database + h2 + + + org.mariadb.jdbc + mariadb-java-client + + + com.github.fge + json-patch + + + org.xmlunit + xmlunit-core + + + org.hibernate + hibernate-testing + test + + + + diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutor.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutor.kt new file mode 100644 index 000000000..eafcaf44b --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutor.kt @@ -0,0 +1,262 @@ +/* + * Copyright © 2019 Bell Canada. + * Modifications Copyright © 2018-2019 IBM. + * + * 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor + +import com.github.fge.jsonpatch.diff.JsonDiff +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot.Status.RUNNING +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot.Status.CANDIDATE +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshotService +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction +import org.onap.ccsdk.cds.controllerblueprints.core.* +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Component +import org.w3c.dom.Node +import org.xmlunit.builder.DiffBuilder +import org.xmlunit.builder.Input +import org.xmlunit.diff.* + + +/** + * ComponentConfigSnapshotsExecutor + * + * Component that retrieves the saved configuration snapshot as identified by the input parameters, + * named resource-id and resource-type. + * + * It reports the content of the requested snapshot via properties, config-snapshot-status + * and config-snapshot-value. In case of error, details can be found in the config-snapshot-status + * and config-snapshot-message properties + * + * @author Serge Simard + */ +@Component("component-config-snapshots-executor") +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +open class ComponentConfigSnapshotsExecutor(private val cfgSnapshotService: ResourceConfigSnapshotService) : + AbstractComponentFunction() { + + companion object { + private val log = LoggerFactory.getLogger(ComponentConfigSnapshotsExecutor::class.java) + + // input fields names accepted by this executor + const val INPUT_OPERATION = "operation" + const val INPUT_RESOURCE_ID = "resource-id" + const val INPUT_RESOURCE_TYPE = "resource-type" + const val INPUT_RESOURCE_STATUS = "resource-status" + const val INPUT_RESOURCE_SNAPSHOT = "resource-snapshot" + const val INPUT_DIFF_CONTENT_TYPE = "diff-content-type" + + const val OPERATION_FETCH = "fetch" + const val OPERATION_STORE = "store" + const val OPERATION_DIFF = "diff" + + const val DIFF_JSON = "JSON" + const val DIFF_XML = "XML" + + // output fields names (and values) populated by this executor. + const val OUTPUT_STATUS = "config-snapshot-status" + const val OUTPUT_MESSAGE = "config-snapshot-message" + const val OUTPUT_SNAPSHOT = "config-snapshot-value" + + const val OUTPUT_STATUS_SUCCESS = "success" + const val OUTPUT_STATUS_ERROR = "error" + } + + /** + * Main entry point for ComponentConfigSnapshotsExecutor + * Supports 3 operations : fetch, store or diff + */ + override suspend fun processNB(executionRequest: ExecutionServiceInput) { + + val operation = getOptionalOperationInput(INPUT_OPERATION)?.returnNullIfMissing()?.textValue() ?: "" + val contentType = getOptionalOperationInput(INPUT_DIFF_CONTENT_TYPE)?.returnNullIfMissing()?.textValue() ?: "" + val resourceId = getOptionalOperationInput(INPUT_RESOURCE_ID)?.returnNullIfMissing()?.textValue() ?: "" + val resourceType = getOptionalOperationInput(INPUT_RESOURCE_TYPE)?.returnNullIfMissing()?.textValue() ?: "" + val resourceStatus = getOptionalOperationInput(INPUT_RESOURCE_STATUS)?.returnNullIfMissing()?.textValue() ?: RUNNING.name + val snapshot = getOptionalOperationInput(INPUT_RESOURCE_SNAPSHOT)?.returnNullIfMissing()?.textValue() ?: "" + val status = ResourceConfigSnapshot.Status.valueOf(resourceStatus) + + when (operation) { + OPERATION_FETCH -> fetchConfigurationSnapshot(resourceId, resourceType, status) + OPERATION_STORE -> storeConfigurationSnapshot(snapshot, resourceId, resourceType, status) + OPERATION_DIFF -> compareConfigurationSnapshot(resourceId, resourceType, contentType) + + else -> setNodeOutputErrors(OUTPUT_STATUS_ERROR, + "Operation parameter must be fetch, store or diff") + } + } + + /** + * General error handling for the executor. + */ + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + setNodeOutputErrors(OUTPUT_STATUS_ERROR, "Error : ${runtimeException.message}") + } + + /** + * Fetch a configuration snapshot, for resource identified by ID/type, of type status (RUNNING by default) + */ + private suspend fun fetchConfigurationSnapshot(resourceId: String, resourceType: String, + status : ResourceConfigSnapshot.Status = RUNNING) { + try { + val cfgSnapshotValue = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, status) + setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, cfgSnapshotValue) + } catch (er : NoSuchElementException) { + val message = "No Resource config snapshot identified by resourceId={$resourceId}, " + + "resourceType={$resourceType} does not exists" + setNodeOutputErrors(OUTPUT_STATUS_ERROR, message) + } + } + + /** + * Store a configuration snapshot, for resource identified by ID/type, of type status (RUNNING by default) + */ + private suspend fun storeConfigurationSnapshot(cfgSnapshotValue : String, resourceId: String, resourceType: String, + status : ResourceConfigSnapshot.Status = RUNNING) { + if (cfgSnapshotValue.isNotEmpty()) { + val cfgSnapshotSaved = cfgSnapshotService.write(cfgSnapshotValue, resourceId, resourceType, status) + setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, cfgSnapshotSaved.config_snapshot ?: "" ) + } else { + val message = "Could not store config snapshot identified by resourceId={$resourceId},resourceType={$resourceType} does not exists" + setNodeOutputErrors(OUTPUT_STATUS_ERROR, message) + } + } + + /** + * Compare two configs (RUNNING vs CANDIDATE) for resource identified by ID/type, using the specified contentType + */ + private suspend fun compareConfigurationSnapshot(resourceId: String, resourceType: String, contentType : String) { + + when (contentType.toUpperCase()) { + DIFF_JSON -> { + val cfgRunning = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, RUNNING) + val cfgCandidate = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, CANDIDATE) + + val patchNode = JsonDiff.asJson(cfgRunning.jsonAsJsonType(), cfgCandidate.jsonAsJsonType()) + setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, patchNode.toString()) + } + DIFF_XML -> { + val cfgRunning = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, RUNNING) + val cfgCandidate = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, CANDIDATE) + + val myDiff = DiffBuilder + .compare(Input.fromString(cfgRunning)) + .withTest(Input.fromString(cfgCandidate)) + .checkForSimilar() + .ignoreComments() + .ignoreWhitespace() + .normalizeWhitespace() + .build() + + setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, formatXmlDifferences(myDiff)) + } + else -> { + val message = "Could not compare config snapshots for type $contentType" + setNodeOutputErrors(OUTPUT_STATUS_ERROR, message) + } + } + } + + /** + * Utility function to set the output properties of the executor node + */ + private fun setNodeOutputProperties(status: String, snapshot: String) { + setAttribute(OUTPUT_STATUS, status.asJsonPrimitive()) + setAttribute(OUTPUT_SNAPSHOT, snapshot.asJsonPrimitive()) + log.info("Setting output $OUTPUT_STATUS=$status, $OUTPUT_SNAPSHOT=$snapshot ") + } + + /** + * Utility function to set the output properties and errors of the executor node, in case of errors + */ + private fun setNodeOutputErrors(status: String, message: String) { + setAttribute(OUTPUT_STATUS, status.asJsonPrimitive()) + setAttribute(OUTPUT_MESSAGE, message.asJsonPrimitive()) + setAttribute(OUTPUT_SNAPSHOT, "".asJsonPrimitive()) + + log.info("Setting error and output $OUTPUT_STATUS=$status, $OUTPUT_MESSAGE=$message ") + + addError(status, OUTPUT_MESSAGE, message) + } + + /** + * Formats XmlUnit differences into xml-patch like response (RFC5261) + */ + private fun formatXmlDifferences(differences : Diff) : String { + val output = StringBuilder() + output.append("" + + "") + val diffIterator = differences.getDifferences().iterator() + while (diffIterator.hasNext()) { + + val aDiff = diffIterator.next().comparison + when (aDiff.type) { + ComparisonType.ATTR_VALUE -> { + output.append("") + .append(aDiff.testDetails.value) + .append("") + } + ComparisonType.TEXT_VALUE -> { + output.append("") + .append(aDiff.testDetails.value) + .append("") + } + ComparisonType.CHILD_LOOKUP -> { + output.append("") + .append(formatNode(aDiff.testDetails.target)) + .append("") + } + ComparisonType.CHILD_NODELIST_LENGTH -> { + // Ignored; will be processed in the CHILD_LOOKUP case + } + else -> { + log.warn("Unsupported XML difference found: $aDiff") + } + } + } + output.append("") + + return output.toString() + } + + /** + * Formats a node value obtained from an XmlUnit differences node + */ + private fun formatNode(node: Node): String { + val output = StringBuilder() + + val parentName = node.localName + output.append("<$parentName>") + if (node.hasChildNodes()) { + val nodes = node.childNodes + for (index in 1..nodes.length) { + val child = nodes.item(index-1) + if (child.nodeType == Node.TEXT_NODE || child.nodeType == Node.COMMENT_NODE) { + output.append(child.nodeValue) + } else { + output.append(formatNode(child)) + } + } + } + output.append("") + + return output.toString() + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshot.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshot.kt new file mode 100644 index 000000000..36a547032 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshot.kt @@ -0,0 +1,85 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db + +import com.fasterxml.jackson.annotation.JsonFormat +import io.swagger.annotations.ApiModelProperty +import org.hibernate.annotations.Proxy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.io.Serializable +import java.util.* +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.EntityListeners +import javax.persistence.Id +import javax.persistence.Lob +import javax.persistence.Table +import javax.persistence.Temporal +import javax.persistence.TemporalType + +/** + * ResourceConfigSnapshot model + * Stores RUNNING or CANDIDATE resource configuration snapshots, captured during the execution + * of blueprints. A resource is identified by an identifier and a type. + * + * @author Serge Simard + * @version 1.0 + */ +@EntityListeners(AuditingEntityListener::class) +@Entity +@Table(name = "RESOURCE_CONFIG_SNAPSHOT") +@Proxy(lazy = false) +class ResourceConfigSnapshot : Serializable { + + @get:ApiModelProperty(value = "Resource type.", required = true, example = "ServiceInstance, VfModule, VNF, PNF") + @Column(name = "resource_type", nullable = false) + var resourceType: String? = null + + @get:ApiModelProperty(value = "ID associated with the resource type in the inventory system.", required = true) + @Column(name = "resource_id", nullable = false) + var resourceId: String? = null + + @get:ApiModelProperty(value = "Status of the snapshot, either running or candidate.", required = true) + @Column(name = "status", nullable = false) + var status: Status? = null + + @get:ApiModelProperty(value = "Snapshot of the resource as retrieved from resource.", required = true) + @Lob + @Column(name = "config_snapshot", nullable = false) + var config_snapshot: String? = null + + @Id + @Column(name = "resource_config_snapshot_id") + var id: String? = null + + @get:ApiModelProperty(value = "Creation date of the snapshot.", required = true) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + @LastModifiedDate + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "creation_date") + var createdDate = Date() + + companion object { + private const val serialVersionUID = 1L + } + + enum class Status(val state: String) { + RUNNING("RUNNING"), + CANDIDATE("CANDIDATE") + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotRepository.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotRepository.kt new file mode 100644 index 000000000..4ab7d7f0e --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotRepository.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 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. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db + +import org.springframework.data.jpa.repository.JpaRepository +import javax.transaction.Transactional + +/** + * JPA repository managing the underlying ResourceConfigSnapshot table. + * + * @author Serge Simard + * @version 1.0 + */ +interface ResourceConfigSnapshotRepository : JpaRepository { + + fun findByResourceIdAndResourceTypeAndStatus( + resourceId: String, + resourceType: String, + status : ResourceConfigSnapshot.Status): ResourceConfigSnapshot? + + @Transactional + fun deleteByResourceIdAndResourceTypeAndStatus( + resourceId: String, + resourceType: String, + status : ResourceConfigSnapshot.Status) +} diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotService.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotService.kt new file mode 100644 index 000000000..50c90f332 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotService.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 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. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot.Status.RUNNING +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException +import org.slf4j.LoggerFactory +import org.springframework.dao.DataIntegrityViolationException +import org.springframework.stereotype.Service +import java.util.* +import kotlin.NoSuchElementException + +/** + * ResourceConfigSnapshot managing service. + * + * @author Serge Simard + * @version 1.0 + */ +@Service +class ResourceConfigSnapshotService(private val repository: ResourceConfigSnapshotRepository) { + + private val log = LoggerFactory.getLogger(ResourceConfigSnapshotService::class.toString()) + + suspend fun findByResourceIdAndResourceTypeAndStatus(resourceId: String, resourceType: String, + status : ResourceConfigSnapshot.Status = RUNNING): String = + withContext(Dispatchers.IO) { + repository.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, status) + ?.config_snapshot ?: throw NoSuchElementException() + } + + suspend fun write(snapshot: String, resId: String, resType: String, + status: ResourceConfigSnapshot.Status = RUNNING) : ResourceConfigSnapshot = + withContext(Dispatchers.IO) { + + val resourceConfigSnapshotEntry = ResourceConfigSnapshot() + resourceConfigSnapshotEntry.id = UUID.randomUUID().toString() + resourceConfigSnapshotEntry.resourceId = resId + resourceConfigSnapshotEntry.resourceType = resType + resourceConfigSnapshotEntry.status = status + resourceConfigSnapshotEntry.config_snapshot = snapshot + + // Overwrite configuration snapshot entry of resId/resType + if (resId.isNotEmpty() && resType.isNotEmpty()) { + repository.findByResourceIdAndResourceTypeAndStatus(resId, resType, status)?. + let { + log.info("Overwriting configuration snapshot entry for resourceId=($resId), " + + "resourceType=($resType), status=($status)") + repository.deleteByResourceIdAndResourceTypeAndStatus(resId, resType, status) + } + } + var storedSnapshot: ResourceConfigSnapshot + try { + storedSnapshot = repository.saveAndFlush(resourceConfigSnapshotEntry) + log.info("Stored configuration snapshot for resourceId=($resId), " + + "resourceType=($resType), status=($status), " + + "dated=(${storedSnapshot.createdDate})") + } catch (ex: DataIntegrityViolationException) { + throw BluePrintException("Failed to store configuration snapshot entry.", ex) + } + storedSnapshot + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutorTest.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutorTest.kt new file mode 100644 index 000000000..79dd93037 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutorTest.kt @@ -0,0 +1,365 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor + +import com.fasterxml.jackson.databind.JsonNode +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintProperties +import org.onap.ccsdk.cds.blueprintsprocessor.core.BlueprintPropertyConfiguration +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.db.BluePrintDBLibConfiguration +import org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor.ComponentConfigSnapshotsExecutor.Companion.DIFF_JSON +import org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor.ComponentConfigSnapshotsExecutor.Companion.DIFF_XML +import org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor.ComponentConfigSnapshotsExecutor.Companion.OPERATION_DIFF +import org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor.ComponentConfigSnapshotsExecutor.Companion.OPERATION_FETCH +import org.onap.ccsdk.cds.blueprintsprocessor.functions.ansible.executor.ComponentConfigSnapshotsExecutor.Companion.OPERATION_STORE +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshotService +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException +import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive +import org.onap.ccsdk.cds.controllerblueprints.core.config.BluePrintLoadConfiguration +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit4.SpringRunner + +@RunWith(SpringRunner::class) +@ContextConfiguration(classes = [ResourceConfigSnapshotService::class, + BlueprintPropertyConfiguration::class, BluePrintProperties::class, + BluePrintDBLibConfiguration::class, BluePrintLoadConfiguration::class]) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@EnableAutoConfiguration +@Suppress("SameParameterValue") +class ComponentConfigSnapshotsExecutorTest { + + @Autowired + lateinit var cfgSnapshotService : ResourceConfigSnapshotService + lateinit var cfgSnapshotComponent : ComponentConfigSnapshotsExecutor + private var bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime("123456-1000", + "./../../../../components/model-catalog/blueprint-model/test-blueprint/remote_scripts") + + private val resourceId = "1" + private val resourceType = "ServiceInstance" + private val props = mutableMapOf() + private val nodeTemplateName = "nodeTemplateName" + + private val executionRequest = ExecutionServiceInput() + + @Before + fun setup() { + cfgSnapshotComponent = ComponentConfigSnapshotsExecutor(cfgSnapshotService) + props[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_ID] = resourceId.asJsonPrimitive() + props[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_TYPE] = resourceType.asJsonPrimitive() + + + cfgSnapshotComponent.operationInputs = props + cfgSnapshotComponent.bluePrintRuntimeService = bluePrintRuntimeService + cfgSnapshotComponent.nodeTemplateName = nodeTemplateName + + cfgSnapshotComponent.executionServiceInput = executionRequest + cfgSnapshotComponent.processId = "12" + cfgSnapshotComponent.workflowName = "workflow" + cfgSnapshotComponent.stepName = "step" + cfgSnapshotComponent.interfaceName = "interfaceName" + cfgSnapshotComponent.operationName = "operationName" + } + + @Test + fun processNBFetchWithResourceIdAndResourceTypeSingleFind() { + val snapshot = ResourceConfigSnapshot() + val snapshotConfig = "TEST1" + snapshot.config_snapshot = snapshotConfig + + runBlocking { + try { + val resId = "121111" + val resType = "PNF" + cfgSnapshotService.write(snapshotConfig, resId, resType) + prepareRequestProperties(OPERATION_FETCH, resId, resType, ResourceConfigSnapshot.Status.RUNNING.name) + + cfgSnapshotComponent.processNB(executionRequest) + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + // then; we should get success and the TEST1 payload in our output properties + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_SUCCESS.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + assertEquals(snapshotConfig.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_SNAPSHOT)) + } + } + + @Test + fun processNBFetchCandidateWithResourceIdAndResourceTypeSingleFind() { + val snapshot = ResourceConfigSnapshot() + val snapshotConfig = "TEST" + snapshot.config_snapshot = snapshotConfig + + runBlocking { + try { + val resId = "121111" + val resType = "PNF" + cfgSnapshotService.write(snapshotConfig, resId, resType, ResourceConfigSnapshot.Status.CANDIDATE) + prepareRequestProperties(OPERATION_FETCH, resId, resType, ResourceConfigSnapshot.Status.CANDIDATE.name) + + cfgSnapshotComponent.processNB(executionRequest) + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + // then; we should get success and the TEST payload in our output properties + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_SUCCESS.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + assertEquals(snapshotConfig.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_SNAPSHOT)) + } + } + + @Test + fun processNBStoreWithResourceIdAndResourceType() { + val snapshot = ResourceConfigSnapshot() + val snapshotConfig = "PAYLOAD" + snapshot.config_snapshot = snapshotConfig + + runBlocking { + try { + val resId = "121111" + val resType = "PNF" + prepareRequestProperties(OPERATION_STORE, resId, resType, snapshotConfig) + + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get success and the PAYLOAD payload in our output properties + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_SUCCESS.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + assertEquals(snapshotConfig.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_SNAPSHOT)) + } + } + + @Test + fun processNBFetchNoneFound() { + + runBlocking { + // when; asking for unknown resource Id/ resource Type combo; should get an error response + try { + prepareRequestProperties(OPERATION_FETCH, "asdasd", "PNF", ResourceConfigSnapshot.Status.RUNNING.name) + + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get error and the PAYLOAD payload in our output properties + assertTrue( bluePrintRuntimeService.getBluePrintError().errors.size > 0 ) + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_ERROR.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + } + } + + @Test + fun processNBErrorOperationUnknown() { + + runBlocking { + // when; asking for unknown operation update; should get an error response + try { + prepareRequestProperties("update", "asdasd", "PNF", ResourceConfigSnapshot.Status.RUNNING.name) + + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get error in our output properties + assertTrue( bluePrintRuntimeService.getBluePrintError().errors.size == 1 ) + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_ERROR.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + val msg = "Operation parameter must be fetch, store or diff" + assertEquals(msg.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_MESSAGE)) + } + } + + @Test + fun processNBErrorDiffContentTypeUnknown() { + + runBlocking { + // when; asking for unknown content type diff operation; should get an error response + try { + prepareRequestProperties(OPERATION_DIFF, "asdasd", "PNF", "YANG") + + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot lookup: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get error in our output properties + assertTrue( bluePrintRuntimeService.getBluePrintError().errors.size == 1 ) + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_ERROR.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + val message = "Could not compare config snapshots for type YANG" + assertEquals(message.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_MESSAGE)) + } + } + + @Test + fun processNBCompareTwoJsonConfigSnapshots() { + + runBlocking { + + // when; comparing RUNNING vs CANDIDATE json configs; should get an success response; with differences + try { + val resId = "131313" + val resType = "PNF" + preparePayload("config-payload-running.json", resId, resType, ResourceConfigSnapshot.Status.RUNNING) + preparePayload("config-payload-candidate.json", resId, resType, ResourceConfigSnapshot.Status.CANDIDATE) + + prepareRequestProperties(OPERATION_DIFF, resId, resType, DIFF_JSON) + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot diff: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get success + assertTrue( bluePrintRuntimeService.getBluePrintError().errors.size == 0 ) + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_SUCCESS.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + + // then; we should get JSON-patches differences in our response property + val diffJson = "[{\"op\":\"add\",\"path\":\"/system-uptime-information/last-configured-time/new-child-object\",\"value\":{\"property\":\"value\"}}," + + "{\"op\":\"replace\",\"path\":\"/system-uptime-information/system-booted-time/time-length\",\"value\":\"14:52:54\"}," + + "{\"op\":\"replace\",\"path\":\"/system-uptime-information/time-source\",\"value\":\" DNS CLOCK \"}," + + "{\"op\":\"add\",\"path\":\"/system-uptime-information/uptime-information/load-average-10\",\"value\":\"0.05\"}]" + assertEquals(diffJson.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_SNAPSHOT)) + } + } + + @Test + fun processNBCompareTwoXmlConfigSnapshots() { + + runBlocking { + + // when; comparing RUNNING vs CANDIDATE xml configs; should get an success response; with differences + try { + val resId = "141414" + val resType = "VNF" + preparePayload("interface-running.xml", resId, resType, ResourceConfigSnapshot.Status.RUNNING) + preparePayload("interface-candidate.xml", resId, resType, ResourceConfigSnapshot.Status.CANDIDATE) + + prepareRequestProperties(OPERATION_DIFF, resId, resType, DIFF_XML) + + cfgSnapshotComponent.processNB(executionRequest) + + } catch (e: BluePrintProcessorException) { + kotlin.test.assertEquals("Can't proceed with the cfg snapshot diff: provide resource-id and resource-type.", + e.message) + return@runBlocking + } + + // then; we should get success + assertTrue( bluePrintRuntimeService.getBluePrintError().errors.size == 0 ) + assertEquals(ComponentConfigSnapshotsExecutor.OUTPUT_STATUS_SUCCESS.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_STATUS)) + + // then; we should get XML-patches differences in our response property + val diffXml = "" + + "" + + "2343" + + "34" + + "09098789" + + "2828828" + + "TEGig400-int01" + + "" + assertEquals(diffXml.asJsonPrimitive(), + bluePrintRuntimeService.getNodeTemplateAttributeValue(nodeTemplateName, + ComponentConfigSnapshotsExecutor.OUTPUT_SNAPSHOT)) } + } + + private fun preparePayload(filename : String, resId : String, resType : String, status: ResourceConfigSnapshot.Status) { + runBlocking { + cfgSnapshotService.write(JacksonUtils.getClassPathFileContent("payload/requests/$filename"), resId, resType, status) + } + } + + private fun prepareRequestProperties (oper : String, resId : String, resType : String, optional: String = "") { + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_OPERATION] = oper.asJsonPrimitive() + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_ID] = resId.asJsonPrimitive() + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_TYPE] = resType.asJsonPrimitive() + + // Optional inputs + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_DIFF_CONTENT_TYPE] = "".asJsonPrimitive() + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_SNAPSHOT] = "".asJsonPrimitive() + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_STATUS] = + ResourceConfigSnapshot.Status.RUNNING.name.asJsonPrimitive() + + when (oper) { + OPERATION_DIFF -> + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_DIFF_CONTENT_TYPE] = optional.asJsonPrimitive() + OPERATION_STORE -> + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_SNAPSHOT] = optional.asJsonPrimitive() + OPERATION_FETCH -> + cfgSnapshotComponent.operationInputs[ComponentConfigSnapshotsExecutor.INPUT_RESOURCE_STATUS] = optional.asJsonPrimitive() + } + } +} diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotServiceTest.kt b/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotServiceTest.kt new file mode 100644 index 000000000..2830cb547 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotServiceTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ResourceConfigSnapshotServiceTest { + + private val cfgRepository = mockk() + + private val cfgService = ResourceConfigSnapshotService(cfgRepository) + + private val resourceId = "1" + private val resourceType = "PNF" + private val configSnapshot = "config_snapshot" + private val resourceStatus = ResourceConfigSnapshot.Status.RUNNING + + @Test + fun findByResourceIdAndResourceTypeTest() { + val tr = ResourceConfigSnapshot() + tr.config_snapshot = "res" + runBlocking { + every { + cfgRepository.findByResourceIdAndResourceTypeAndStatus(any(), any(), any()) + } returns tr + val res = cfgService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType) + assertEquals(tr.config_snapshot, res) + } + } + + @Test(expected = NoSuchElementException::class) + fun notFoundEntryReturnsExceptionTest() { + val tr = ResourceConfigSnapshot() + runBlocking { + every { + cfgRepository.findByResourceIdAndResourceTypeAndStatus(any(), any(), any()) + } returns tr + val snap = cfgService.findByResourceIdAndResourceTypeAndStatus("MISSING_ID", "UNKNOWN_TYPE") + assertTrue ( snap.isBlank(), "Not found but returned a non empty string" ) + } + } + + @Test + fun createNewResourceConfigSnapshotTest() { + val tr = ResourceConfigSnapshot() + runBlocking { + every { cfgRepository.saveAndFlush(any()) } returns tr + every { + cfgRepository.findByResourceIdAndResourceTypeAndStatus(any(), any(), any()) + } returns null + val res = cfgService.write( configSnapshot, resourceId, resourceType, resourceStatus) + assertEquals(tr, res) + } + } + + @Test + fun updateExistingResourceConfigSnapshotTest() { + val tr = ResourceConfigSnapshot() + runBlocking { + every { cfgRepository.saveAndFlush(any()) } returns tr + every { + cfgRepository.findByResourceIdAndResourceTypeAndStatus(any(), any(), any()) + } returns tr + every { + cfgRepository.deleteByResourceIdAndResourceTypeAndStatus(any(), any(), any()) + } returns Unit + val res = cfgService.write( configSnapshot, resourceId, resourceType) + verify { + cfgRepository.deleteByResourceIdAndResourceTypeAndStatus(eq(resourceId), eq(resourceType), eq(resourceStatus)) + } + assertEquals(tr, res) + } + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/application-test.properties b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/application-test.properties new file mode 100644 index 000000000..ce5b4e39f --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/application-test.properties @@ -0,0 +1,32 @@ +# Copyright © 2019 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. + +blueprintsprocessor.db.primary.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +blueprintsprocessor.db.primary.username=sa +blueprintsprocessor.db.primary.password= +blueprintsprocessor.db.primary.driverClassName=org.h2.Driver +blueprintsprocessor.db.primary.hibernateHbm2ddlAuto=create-drop +blueprintsprocessor.db.primary.hibernateDDLAuto=update +blueprintsprocessor.db.primary.hibernateNamingStrategy=org.hibernate.cfg.ImprovedNamingStrategy +blueprintsprocessor.db.primary.hibernateDialect=org.hibernate.dialect.H2Dialect +# Controller Blueprints Core Configuration +blueprintsprocessor.blueprintDeployPath=./target/blueprints/deploy +blueprintsprocessor.blueprintArchivePath=./target/blueprints/archive + +# Python executor +blueprints.processor.functions.python.executor.executionPath=./../../../../components/scripts/python/ccsdk_blueprints +blueprints.processor.functions.python.executor.modulePaths=./../../../../components/scripts/python/ccsdk_blueprints + +# Executor Options +blueprintprocessor.netconfExecutor.enabled=true \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/keystore.p12 b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/keystore.p12 new file mode 100644 index 000000000..96b0d3ac3 Binary files /dev/null and b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/keystore.p12 differ diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/logback-test.xml b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/logback-test.xml new file mode 100644 index 000000000..f33adcdb8 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/logback-test.xml @@ -0,0 +1,35 @@ + + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{55} - %msg%n + + + + + + + + + + + + + diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-candidate.json b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-candidate.json new file mode 100644 index 000000000..ad012878d --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-candidate.json @@ -0,0 +1,39 @@ +{ + "system-uptime-information" : + { + "current-time" : + { + "date-time" : "2018-05-15 13:49:56 PDT" + }, + "time-source" : " DNS CLOCK ", + "system-booted-time" : + { + "date-time" : "2018-05-15 10:57:02 PDT", + "time-length" : "14:52:54" + }, + "protocols-started-time" : + { + "date-time" : "2018-05-15 10:59:33 PDT", + "time-length" : "02:50:23" + }, + "last-configured-time" : + { + "date-time" : "2018-05-15 13:49:40 PDT", + "time-length" : "00:00:16", + "user" : "admin", + "new-child-object" : { + "property" : "value" + } + }, + "uptime-information" : + { + "date-time" : "1:49PM", + "up-time" : "2:53", + "active-user-count" : "1", + "load-average-1" : "0.00", + "load-average-5" : "0.06", + "load-average-10" : "0.05", + "load-average-15" : "0.06" + } + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-running.json b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-running.json new file mode 100644 index 000000000..9857eb978 --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/config-payload-running.json @@ -0,0 +1,35 @@ +{ + "system-uptime-information" : + { + "current-time" : + { + "date-time" : "2018-05-15 13:49:56 PDT" + }, + "time-source" : " NTP CLOCK ", + "system-booted-time" : + { + "date-time" : "2018-05-15 10:57:02 PDT", + "time-length" : "02:52:54" + }, + "protocols-started-time" : + { + "date-time" : "2018-05-15 10:59:33 PDT", + "time-length" : "02:50:23" + }, + "last-configured-time" : + { + "date-time" : "2018-05-15 13:49:40 PDT", + "time-length" : "00:00:16", + "user" : "admin" + }, + "uptime-information" : + { + "date-time" : "1:49PM", + "up-time" : "2:53", + "active-user-count" : "1", + "load-average-1" : "0.00", + "load-average-5" : "0.06", + "load-average-15" : "0.06" + } + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-candidate.xml b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-candidate.xml new file mode 100644 index 000000000..d0673aa4e --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-candidate.xml @@ -0,0 +1,34 @@ + + + + + + + Full-Duplex + + + + 34 + + 09098789 + 2828828 + + + TEGig400-int01 + + + \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-running.xml b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-running.xml new file mode 100644 index 000000000..3d875c8fb --- /dev/null +++ b/ms/blueprintsprocessor/functions/config-snapshots/src/test/resources/payload/requests/interface-running.xml @@ -0,0 +1,32 @@ + + + + + + + Full-Duplex + + + + Never + + 0 + 0 + + + + \ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/pom.xml b/ms/blueprintsprocessor/functions/pom.xml index 819373ffd..a5790b985 100755 --- a/ms/blueprintsprocessor/functions/pom.xml +++ b/ms/blueprintsprocessor/functions/pom.xml @@ -34,6 +34,7 @@ netconf-executor restconf-executor cli-executor + config-snapshots diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/pom.xml b/ms/blueprintsprocessor/modules/inbounds/configs-api/pom.xml new file mode 100644 index 000000000..d6dacb595 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.onap.ccsdk.cds.blueprintsprocessor + inbounds + 0.5.2-SNAPSHOT + + + configs-api + jar + Blueprints Processor Resource Configurations API + Blueprints Processor Resource Configurations API + + + + org.onap.ccsdk.cds.blueprintsprocessor.functions + config-snapshots + ${project.parent.version} + + + org.springframework.security + spring-security-core + + + org.onap.ccsdk.cds.controllerblueprints + blueprint-core + + + com.h2database + h2 + test + + + diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotController.kt b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotController.kt new file mode 100644 index 000000000..eb7929509 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotController.kt @@ -0,0 +1,124 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.configs.api + +import com.fasterxml.jackson.databind.JsonNode +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import kotlinx.coroutines.runBlocking +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot +import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshotService +import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.* + +/** + * Exposes Resource Configuration Snapshot API to store and retrieve stored resource configurations. + * + * @author Serge Simard + * @version 1.0 + */ +@RestController +@RequestMapping("/api/v1/configs") +@Api(value = "/api/v1/configs", + description = "Interaction with stored configurations.") +open class ResourceConfigSnapshotController(private val resourceConfigSnapshotService: ResourceConfigSnapshotService) { + + @RequestMapping(path = ["/health-check"], + method = [RequestMethod.GET], + produces = [MediaType.APPLICATION_JSON_VALUE]) + @ResponseBody + @ApiOperation(value = "Health Check", hidden = true) + fun ressCfgSnapshotControllerHealthCheck(): JsonNode = runBlocking { + "Success".asJsonPrimitive() + } + + @RequestMapping(path = [""], + method = [RequestMethod.GET], + produces = [MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE]) + @ApiOperation(value = "Retrieve a resource configuration snapshot.", + notes = "Retrieve a config snapshot, identified by its Resource Id and Type. " + + "An extra 'format' parameter can be passed to tell what content-type is expected.") + @ResponseBody + @PreAuthorize("hasRole('USER')") + fun get( + @ApiParam(value = "Resource Type associated of the resource configuration snapshot.", required = false) + @RequestParam(value = "resourceType", required = true) resourceType: String, + + @ApiParam(value = "Resource Id associated of the resource configuration snapshot.", required = false) + @RequestParam(value = "resourceId", required = true) resourceId: String, + + @ApiParam(value = "Status of the snapshot being retrieved.", defaultValue = "RUNNING", required = false) + @RequestParam(value = "status", required = false, defaultValue = "RUNNING") status: String, + + @ApiParam(value = "Expected format of the snapshot being retrieved.", defaultValue = MediaType.TEXT_PLAIN_VALUE, + required = false) + @RequestParam(value = "format", required = false, defaultValue = MediaType.TEXT_PLAIN_VALUE) format: String) + + : ResponseEntity = runBlocking { + + var configSnapshot = "" + + if (resourceType.isNotEmpty() && resourceId.isNotEmpty()) { + try { + configSnapshot = resourceConfigSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, + resourceType, ResourceConfigSnapshot.Status.valueOf(status.toUpperCase())) + } catch (ex : NoSuchElementException) { + throw ResourceConfigSnapshotException( + "Could not find configuration snapshot entry for type $resourceType and Id $resourceId") + } + } else { + throw IllegalArgumentException("Missing param. You must specify resource-id and resource-type.") + } + + var expectedContentType = format + if (expectedContentType.indexOf('/') < 0) { + expectedContentType = "application/$expectedContentType" + } + val expectedMediaType: MediaType = MediaType.valueOf(expectedContentType) + + ResponseEntity.ok().contentType(expectedMediaType).body(configSnapshot) + } + + @PostMapping("/{resourceType}/{resourceId}/{status}", + produces = [MediaType.APPLICATION_JSON_VALUE]) + @ApiOperation(value = "Store a resource configuration snapshot identified by resourceId, resourceType, status.", + notes = "Store a resource configuration snapshot, identified by its resourceId and resourceType, " + + "and optionally its status, either RUNNING or CANDIDATE.", + response = ResourceConfigSnapshot::class, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @PreAuthorize("hasRole('USER')") + fun postWithResourceIdAndResourceType( + @ApiParam(value = "Resource Type associated with the resolution.", required = false) + @PathVariable(value = "resourceType", required = true) resourceType: String, + @ApiParam(value = "Resource Id associated with the resolution.", required = false) + @PathVariable(value = "resourceId", required = true) resourceId: String, + @ApiParam(value = "Status of the snapshot being retrieved.", defaultValue = "RUNNING", required = true) + @PathVariable(value = "status", required = true) status: String, + @ApiParam(value = "Config snapshot to store.", required = true) + @RequestBody snapshot: String): ResponseEntity = runBlocking { + + val resultStored = + resourceConfigSnapshotService.write(snapshot, resourceId, resourceType, + ResourceConfigSnapshot.Status.valueOf(status.toUpperCase())) + + ResponseEntity.ok().body(resultStored) + } +} diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotException.kt b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotException.kt new file mode 100644 index 000000000..1eeea9893 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotException.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 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. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.configs.api + +class ResourceConfigSnapshotException(message: String) : RuntimeException(message) { + var code: Int = 404 +} diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotExceptionHandler.kt b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotExceptionHandler.kt new file mode 100644 index 000000000..d21464ef5 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotExceptionHandler.kt @@ -0,0 +1,118 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.configs.api + +import com.fasterxml.jackson.annotation.JsonFormat +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonTypeName +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException +import org.onap.ccsdk.cds.controllerblueprints.core.data.ErrorCode +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.orm.jpa.JpaObjectRetrievalFailureException +import org.springframework.dao.EmptyResultDataAccessException +import org.springframework.dao.IncorrectResultSizeDataAccessException +import org.springframework.web.server.ServerWebInputException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import java.io.Serializable +import java.util.* + +/** + * Handle exceptions in ResourceConfigSnapshot API and provide relevant HTTP status codes and messages + * + * @author Serge Simard + * @version 1.0 + */ +@RestControllerAdvice("org.onap.ccsdk.cds.blueprintsprocessor.configs.api") +open class ResourceConfigSnapshotExceptionHandler { + + private val log = LoggerFactory.getLogger(ResourceConfigSnapshotExceptionHandler::class.toString()) + + private val debugMsg = "Resource_Config_Snapshot_ExceptionHandler_Error_Message" + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: BluePrintProcessorException): ResponseEntity { + val errorCode = ErrorCode.BLUEPRINT_PATH_MISSING + return returnError(e, errorCode) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: ServerWebInputException): ResponseEntity { + val errorCode = ErrorCode.INVALID_REQUEST_FORMAT + return returnError(e, errorCode, false) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: IllegalArgumentException): ResponseEntity { + val errorCode = ErrorCode.INVALID_REQUEST_FORMAT + return returnError(e, errorCode, false) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: IncorrectResultSizeDataAccessException): ResponseEntity { + val errorCode = ErrorCode.DUPLICATE_DATA + return returnError(e, errorCode) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: EmptyResultDataAccessException): ResponseEntity { + val errorCode = ErrorCode.RESOURCE_NOT_FOUND + return returnError(e, errorCode, false) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: JpaObjectRetrievalFailureException): ResponseEntity { + val errorCode = ErrorCode.RESOURCE_NOT_FOUND + return returnError(e, errorCode, false) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: Exception): ResponseEntity { + val errorCode = ErrorCode.GENERIC_FAILURE + return returnError(e, errorCode) + } + + @ExceptionHandler + fun resourceConfigSnapshotExceptionHandler(e: ResourceConfigSnapshotException): ResponseEntity { + val errorCode = ErrorCode.RESOURCE_NOT_FOUND + return returnError(e, errorCode, false) + } + + fun returnError(e: Exception, errorCode: ErrorCode, toBeLogged: Boolean = true): ResponseEntity { + if (toBeLogged) { + log.error(e.message, e) + } else { + log.error(e.message) + } + val errorMessage = + ErrorMessage(errorCode.message(e.message!!), + errorCode.value, + debugMsg) + return ResponseEntity(errorMessage, HttpStatus.resolve(errorCode.httpCode)!!) + } +} + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonTypeName("errorMessage") +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +class ErrorMessage(var message: String?, var code: Int?, var debugMessage: String?) : Serializable { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + var timestamp = Date() +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotControllerTest.kt b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotControllerTest.kt new file mode 100644 index 000000000..c3f18fcba --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/configs/api/ResourceConfigSnapshotControllerTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright © 2019 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.configs.api + +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintCoreConfiguration +import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.security.SecurityProperties +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest +import org.springframework.context.annotation.ComponentScan +import org.springframework.http.MediaType +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit4.SpringRunner +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.web.reactive.function.BodyInserters +import java.util.* + +@RunWith(SpringRunner::class) +@WebFluxTest +@ContextConfiguration(classes = [BluePrintCoreConfiguration::class, + BluePrintCatalogService::class, SecurityProperties::class]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +class ResourceConfigSnapshotControllerTest { + + private val log = LoggerFactory.getLogger(ResourceConfigSnapshotControllerTest::class.toString()) + + @Autowired + lateinit var webTestClient: WebTestClient + + val resourceId = "fcaa6ac3ff08" + val resourceType = "PNF" + val snapshotData = "PAYLOAD DATA" + + var requestArguments = "resourceId=$resourceId&resourceType=$resourceType" + + @Test + fun `ping return Success`() { + runBlocking { + webTestClient.get().uri("/api/v1/configs/health-check") + .exchange() + .expectStatus().isOk + .expectBody() + .equals("Success") + } + } + + @Test + fun `update configuration is allowed and updates timestamp`() { + runBlocking { + + webTestClient + .post() + .uri("/api/v1/configs/$resourceType/$resourceId/running") + .body(BodyInserters.fromObject(snapshotData)) + .exchange() + .expectStatus().is2xxSuccessful + .expectBody() + .jsonPath("$.createdDate") + .value { println(it) } + + webTestClient + .post() + .uri("/api/v1/configs/$resourceType/$resourceId/running") + .body(BodyInserters.fromObject(snapshotData)) + .exchange() + .expectStatus().is2xxSuccessful + .expectBody() + .jsonPath("$.createdDate") + .value { println(it)} + } + } + + @Test + fun `get returns requested JSON content-type`() { + runBlocking { + post(resourceType, "22", "RUNNING") + get("json", resourceType,"22", "RUNNING") + } + } + + @Test + fun `get returns requested XML content-type`() { + runBlocking { + post(resourceType, "3", "CANDIDATE") + get("xml", resourceType, "3", "CANDIDATE") + } + } + + @Test + fun `get returns 400 error if missing arg`() { + runBlocking { + val arguments = "artifactName=WRONGARG1&resolutionKey=WRONGARG1" + + webTestClient.get().uri("/api/v1/configs?$arguments") + .exchange() + .expectStatus().isBadRequest + } + } + + @Test + fun `get returns 400 error if wrong Status arg`() { + runBlocking { + val arguments = "resourceId=MISSING&resourceType=PNF&status=TOTALLY_WRONG" + + webTestClient.get().uri("/api/v1/configs?$arguments") + .exchange() + .expectStatus().isBadRequest + } + } + + @Test + fun `get returns 404 if entry not found`() { + runBlocking { + + webTestClient + .get() + .uri("/api/v1/configs?resourceId=MISSING&resourceType=PNF") + .exchange() + .expectStatus().isNotFound + } + } + + private fun post( resourceType: String, resourceId: String, status: String) { + webTestClient + .post() + .uri("/api/v1/configs/$resourceType/$resourceId/$status") + .body(BodyInserters.fromObject(snapshotData)) + .exchange() + .expectStatus().is2xxSuccessful + .expectBody() + } + + private fun get(expectedType : String, resourceType: String, resourceId: String, status: String) { + var requestArguments = "resourceId=$resourceId&resourceType=$resourceType&status=$status" + + if (expectedType.isNotEmpty()) { + requestArguments = "$requestArguments&format=$expectedType" + webTestClient + .get() + .uri("/api/v1/configs?$requestArguments") + .exchange() + .expectStatus().is2xxSuccessful + .expectHeader().contentType(MediaType.valueOf("application/$expectedType")) + .expectBody().equals(snapshotData) + } else { + webTestClient + .get() + .uri("/api/v1/configs?$requestArguments") + .exchange() + .expectStatus().is2xxSuccessful + .expectHeader().contentType(MediaType.TEXT_PLAIN) + .expectBody().equals(snapshotData) + } + } +} \ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/application-test.properties b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/application-test.properties new file mode 100644 index 000000000..e02ed89f9 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/application-test.properties @@ -0,0 +1,30 @@ +# Copyright © 2019 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. + +blueprintsprocessor.db.primary.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +blueprintsprocessor.db.primary.username=sa +blueprintsprocessor.db.primary.password= +blueprintsprocessor.db.primary.driverClassName=org.h2.Driver +blueprintsprocessor.db.primary.hibernateHbm2ddlAuto=create-drop +blueprintsprocessor.db.primary.hibernateDDLAuto=update +blueprintsprocessor.db.primary.hibernateNamingStrategy=org.hibernate.cfg.ImprovedNamingStrategy +blueprintsprocessor.db.primary.hibernateDialect=org.hibernate.dialect.H2Dialect +# Controller Blueprints Core Configuration +blueprintsprocessor.blueprintDeployPath=./target/blueprints/deploy +blueprintsprocessor.blueprintWorkingPath=./target/blueprints/work +blueprintsprocessor.blueprintArchivePath=./target/blueprints/archive + +# Python executor +blueprints.processor.functions.python.executor.executionPath=./../../../../components/scripts/python/ccsdk_blueprints +blueprints.processor.functions.python.executor.modulePaths=./../../../../components/scripts/python/ccsdk_blueprints diff --git a/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/logback.xml b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/logback.xml new file mode 100644 index 000000000..ed92b8963 --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/configs-api/src/test/resources/logback.xml @@ -0,0 +1,35 @@ + + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{100} - %msg%n + + + + + + + + + + + + + diff --git a/ms/blueprintsprocessor/modules/inbounds/pom.xml b/ms/blueprintsprocessor/modules/inbounds/pom.xml index 168578207..d539920b0 100644 --- a/ms/blueprintsprocessor/modules/inbounds/pom.xml +++ b/ms/blueprintsprocessor/modules/inbounds/pom.xml @@ -29,6 +29,7 @@ Blueprints Processor Inbounds + configs-api designer-api resource-api selfservice-api diff --git a/ms/blueprintsprocessor/parent/pom.xml b/ms/blueprintsprocessor/parent/pom.xml index 554df8b83..decb4153c 100755 --- a/ms/blueprintsprocessor/parent/pom.xml +++ b/ms/blueprintsprocessor/parent/pom.xml @@ -47,6 +47,8 @@ 2.8.0 5.5.1 1.10.3 + 2.6.3 + 1.9 @@ -370,6 +372,11 @@ + + org.onap.ccsdk.cds.blueprintsprocessor + configs-api + ${project.version} + org.onap.ccsdk.cds.blueprintsprocessor designer-api @@ -422,6 +429,23 @@ cli-executor ${project.version} + + org.onap.ccsdk.cds.blueprintsprocessor.functions + config-snapshots + ${project.version} + + + + + com.github.fge + json-patch + ${json-patch.version} + + + org.xmlunit + xmlunit-core + ${xmlunit.version} + -- cgit 1.2.3-korg