summaryrefslogtreecommitdiffstats
path: root/ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap
diff options
context:
space:
mode:
authorSerge Simard <serge@agilitae.com>2019-08-08 10:55:57 -0400
committerSerge Simard <serge@agilitae.com>2019-08-13 14:57:10 +0000
commit34c424689a52614fb414d65899282497fe25b164 (patch)
tree91fdafc99a280064f0ef19c1576fdcbbae303cfc /ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap
parent571c49342a905616298d923a9d29a201ae4ecd11 (diff)
Resource Configuration Snapshots Executor and API
Issue-ID: CCSDK-1604 Signed-off-by: Serge Simard <serge@agilitae.com> Change-Id: I349c649e941431b48a309123489d26fb22e0e50a Signed-off-by: Serge Simard <serge@agilitae.com>
Diffstat (limited to 'ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap')
-rw-r--r--ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/ComponentConfigSnapshotsExecutor.kt262
-rw-r--r--ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshot.kt85
-rw-r--r--ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotRepository.kt39
-rw-r--r--ms/blueprintsprocessor/functions/config-snapshots/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/config/snapshots/db/ResourceConfigSnapshotService.kt77
4 files changed, 463 insertions, 0 deletions
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+ "<diff>")
+ val diffIterator = differences.getDifferences().iterator()
+ while (diffIterator.hasNext()) {
+
+ val aDiff = diffIterator.next().comparison
+ when (aDiff.type) {
+ ComparisonType.ATTR_VALUE -> {
+ output.append("<replace sel=\"").append(aDiff.testDetails.xPath).append("\">")
+ .append(aDiff.testDetails.value)
+ .append("</replace>")
+ }
+ ComparisonType.TEXT_VALUE -> {
+ output.append("<replace sel=\"").append(aDiff.testDetails.xPath).append("\">")
+ .append(aDiff.testDetails.value)
+ .append("</replace>")
+ }
+ ComparisonType.CHILD_LOOKUP -> {
+ output.append("<add sel=\"").append(aDiff.testDetails.parentXPath).append("\">")
+ .append(formatNode(aDiff.testDetails.target))
+ .append("</add>")
+ }
+ ComparisonType.CHILD_NODELIST_LENGTH -> {
+ // Ignored; will be processed in the CHILD_LOOKUP case
+ }
+ else -> {
+ log.warn("Unsupported XML difference found: $aDiff")
+ }
+ }
+ }
+ output.append("</diff>")
+
+ 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("</$parentName>")
+
+ 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<ResourceConfigSnapshot, String> {
+
+ 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