2 * Copyright © 2019 Bell Canada.
3 * Modifications Copyright © 2018-2019 IBM.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots
20 import com.github.fge.jsonpatch.diff.JsonDiff
21 import org.apache.logging.log4j.util.Strings
22 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
23 import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot.Status.CANDIDATE
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshot.Status.RUNNING
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.config.snapshots.db.ResourceConfigSnapshotService
27 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
28 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
29 import org.onap.ccsdk.cds.controllerblueprints.core.jsonAsJsonType
30 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
31 import org.slf4j.LoggerFactory
32 import org.springframework.beans.factory.config.ConfigurableBeanFactory
33 import org.springframework.context.annotation.Scope
34 import org.springframework.stereotype.Component
35 import org.w3c.dom.Node
36 import org.xmlunit.builder.DiffBuilder
37 import org.xmlunit.builder.Input
38 import org.xmlunit.diff.ComparisonType
39 import org.xmlunit.diff.Diff
42 * ComponentConfigSnapshotsExecutor
44 * Component that retrieves the saved configuration snapshot as identified by the input parameters,
45 * named resource-id and resource-type.
47 * It reports the content of the requested snapshot via properties, config-snapshot-status
48 * and config-snapshot-value. In case of error, details can be found in the config-snapshot-status
49 * and config-snapshot-message properties
51 * @author Serge Simard
53 @Component("component-config-snapshots-executor")
54 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
55 open class ComponentConfigSnapshotsExecutor(private val cfgSnapshotService: ResourceConfigSnapshotService) :
56 AbstractComponentFunction() {
59 private val log = LoggerFactory.getLogger(ComponentConfigSnapshotsExecutor::class.java)
61 // input fields names accepted by this executor
62 const val INPUT_OPERATION = "operation"
63 const val INPUT_RESOURCE_ID = "resource-id"
64 const val INPUT_RESOURCE_TYPE = "resource-type"
65 const val INPUT_RESOURCE_STATUS = "resource-status"
66 const val INPUT_RESOURCE_SNAPSHOT = "resource-snapshot"
67 const val INPUT_DIFF_CONTENT_TYPE = "diff-content-type"
69 const val OPERATION_FETCH = "fetch"
70 const val OPERATION_STORE = "store"
71 const val OPERATION_DIFF = "diff"
73 const val DIFF_JSON = "JSON"
74 const val DIFF_XML = "XML"
76 // output fields names (and values) populated by this executor.
77 const val OUTPUT_STATUS = "config-snapshot-status"
78 const val OUTPUT_MESSAGE = "config-snapshot-message"
79 const val OUTPUT_SNAPSHOT = "config-snapshot-value"
81 const val OUTPUT_STATUS_SUCCESS = "success"
82 const val OUTPUT_STATUS_ERROR = "error"
86 * Main entry point for ComponentConfigSnapshotsExecutor
87 * Supports 3 operations : fetch, store or diff
89 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
91 val operation = getOptionalOperationInput(INPUT_OPERATION)?.returnNullIfMissing()?.textValue() ?: ""
92 val contentType = getOptionalOperationInput(INPUT_DIFF_CONTENT_TYPE)?.returnNullIfMissing()?.textValue() ?: ""
93 val resourceId = getOptionalOperationInput(INPUT_RESOURCE_ID)?.returnNullIfMissing()?.textValue() ?: ""
94 val resourceType = getOptionalOperationInput(INPUT_RESOURCE_TYPE)?.returnNullIfMissing()?.textValue() ?: ""
95 val resourceStatus = getOptionalOperationInput(INPUT_RESOURCE_STATUS)?.returnNullIfMissing()?.textValue() ?: RUNNING.name
96 val snapshot = getOptionalOperationInput(INPUT_RESOURCE_SNAPSHOT)?.returnNullIfMissing()?.textValue() ?: ""
97 val status = ResourceConfigSnapshot.Status.valueOf(resourceStatus)
100 OPERATION_FETCH -> fetchConfigurationSnapshot(resourceId, resourceType, status)
101 OPERATION_STORE -> storeConfigurationSnapshot(snapshot, resourceId, resourceType, status)
102 OPERATION_DIFF -> compareConfigurationSnapshot(resourceId, resourceType, contentType)
104 else -> setNodeOutputErrors(
106 "Operation parameter must be fetch, store or diff"
112 * General error handling for the executor.
114 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
115 setNodeOutputErrors(OUTPUT_STATUS_ERROR, "Error : ${runtimeException.message}")
119 * Fetch a configuration snapshot, for resource identified by ID/type, of type status (RUNNING by default)
121 private suspend fun fetchConfigurationSnapshot(
123 resourceType: String,
124 status: ResourceConfigSnapshot.Status = RUNNING
127 val cfgSnapshotValue = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, status)
128 setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, cfgSnapshotValue)
129 } catch (er: NoSuchElementException) {
130 val message = "No Resource config snapshot identified by resourceId={$resourceId}, " +
131 "resourceType={$resourceType} does not exists"
132 setNodeOutputErrors(OUTPUT_STATUS_ERROR, message)
137 * Store a configuration snapshot, for resource identified by ID/type, of type status (RUNNING by default)
139 private suspend fun storeConfigurationSnapshot(
140 cfgSnapshotValue: String,
142 resourceType: String,
143 status: ResourceConfigSnapshot.Status = RUNNING
145 if (cfgSnapshotValue.isNotEmpty()) {
146 val cfgSnapshotSaved = cfgSnapshotService.write(cfgSnapshotValue, resourceId, resourceType, status)
147 setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, cfgSnapshotSaved.config_snapshot ?: "")
149 val message = "Could not store config snapshot identified by resourceId={$resourceId},resourceType={$resourceType} does not exists"
150 setNodeOutputErrors(OUTPUT_STATUS_ERROR, message)
155 * Compare two configs (RUNNING vs CANDIDATE) for resource identified by ID/type, using the specified contentType
157 private suspend fun compareConfigurationSnapshot(resourceId: String, resourceType: String, contentType: String) {
159 val cfgRunning = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, RUNNING)
160 val cfgCandidate = cfgSnapshotService.findByResourceIdAndResourceTypeAndStatus(resourceId, resourceType, CANDIDATE)
162 if (cfgRunning.isEmpty() || cfgCandidate.isEmpty()) {
163 setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, Strings.EMPTY)
167 when (contentType.toUpperCase()) {
169 val patchNode = JsonDiff.asJson(cfgRunning.jsonAsJsonType(), cfgCandidate.jsonAsJsonType())
170 setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, patchNode.toString())
173 val myDiff = DiffBuilder
174 .compare(Input.fromString(cfgRunning))
175 .withTest(Input.fromString(cfgCandidate))
179 .normalizeWhitespace()
182 setNodeOutputProperties(OUTPUT_STATUS_SUCCESS, formatXmlDifferences(myDiff))
185 val message = "Could not compare config snapshots for type $contentType"
186 setNodeOutputErrors(OUTPUT_STATUS_ERROR, message)
192 * Utility function to set the output properties of the executor node
194 private fun setNodeOutputProperties(status: String, snapshot: String) {
195 setAttribute(OUTPUT_STATUS, status.asJsonPrimitive())
196 setAttribute(OUTPUT_SNAPSHOT, snapshot.asJsonPrimitive())
197 log.debug("Setting output $OUTPUT_STATUS=$status")
201 * Utility function to set the output properties and errors of the executor node, in case of errors
203 private fun setNodeOutputErrors(status: String, message: String) {
204 setAttribute(OUTPUT_STATUS, status.asJsonPrimitive())
205 setAttribute(OUTPUT_MESSAGE, message.asJsonPrimitive())
206 setAttribute(OUTPUT_SNAPSHOT, "".asJsonPrimitive())
208 log.info("Setting error and output $OUTPUT_STATUS=$status, $OUTPUT_MESSAGE=$message ")
210 addError(status, OUTPUT_MESSAGE, message)
214 * Formats XmlUnit differences into xml-patch like response (RFC5261)
216 private fun formatXmlDifferences(differences: Diff): String {
217 val output = StringBuilder()
219 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
222 val diffIterator = differences.getDifferences().iterator()
223 while (diffIterator.hasNext()) {
225 val aDiff = diffIterator.next().comparison
227 ComparisonType.ATTR_VALUE -> {
228 output.append("<replace sel=\"").append(aDiff.testDetails.xPath).append("\">")
229 .append(aDiff.testDetails.value)
230 .append("</replace>")
232 ComparisonType.TEXT_VALUE -> {
233 output.append("<replace sel=\"").append(aDiff.testDetails.xPath).append("\">")
234 .append(aDiff.testDetails.value)
235 .append("</replace>")
237 ComparisonType.CHILD_LOOKUP -> {
238 output.append("<add sel=\"").append(aDiff.testDetails.parentXPath).append("\">")
239 .append(formatNode(aDiff.testDetails.target))
242 ComparisonType.CHILD_NODELIST_LENGTH -> {
243 // Ignored; will be processed in the CHILD_LOOKUP case
246 log.warn("Unsupported XML difference found: $aDiff")
250 output.append("</diff>")
252 return output.toString()
256 * Formats a node value obtained from an XmlUnit differences node
258 private fun formatNode(node: Node): String {
259 val output = StringBuilder()
261 val parentName = node.localName
262 output.append("<$parentName>")
263 if (node.hasChildNodes()) {
264 val nodes = node.childNodes
265 for (index in 1..nodes.length) {
266 val child = nodes.item(index - 1)
267 if (child.nodeType == Node.TEXT_NODE || child.nodeType == Node.COMMENT_NODE) {
268 output.append(child.nodeValue)
270 output.append(formatNode(child))
274 output.append("</$parentName>")
276 return output.toString()