0b629493682d7ee0ee2fc3aa1f3a4872093547f8
[ccsdk/cds.git] /
1 package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template
2
3 import com.fasterxml.jackson.databind.JsonNode
4 import com.fasterxml.jackson.databind.ObjectMapper
5 import com.fasterxml.jackson.databind.node.ArrayNode
6 import com.fasterxml.jackson.databind.node.ObjectNode
7 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
8 import com.fasterxml.jackson.module.kotlin.convertValue
9 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
10 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
11 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
12 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sConfigValueRequest
13 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sPluginInstanceApi
14 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
15 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
16 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
17 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
18 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
19 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
20 import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
21 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
22 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintVelocityTemplateService
23 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
24 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
25 import org.slf4j.LoggerFactory
26 import org.springframework.beans.factory.config.ConfigurableBeanFactory
27 import org.springframework.context.annotation.Scope
28 import org.springframework.stereotype.Component
29 import java.io.File
30 import java.nio.file.Path
31 import java.nio.file.Paths
32
33 @Component("component-k8s-config-value")
34 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
35 open class K8sConfigValueComponent(
36     private var bluePrintPropertiesService: BluePrintPropertiesService,
37     private val resourceResolutionService: ResourceResolutionService
38 ) : AbstractComponentFunction() {
39
40     private val log = LoggerFactory.getLogger(K8sConfigValueComponent::class.java)!!
41
42     companion object {
43         const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
44         const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"
45         const val INPUT_K8S_RB_CONFIG_TEMPLATE_NAME = "k8s-rb-config-template-name"
46         const val INPUT_K8S_RB_CONFIG_NAME = "k8s-rb-config-name"
47         const val INPUT_K8S_RB_CONFIG_VERSION = "k8s-rb-config-version"
48         const val INPUT_K8S_INSTANCE_ID = "k8s-instance-id"
49         const val INPUT_K8S_CONFIG_VALUE_SOURCE = "k8s-rb-config-value-source"
50         const val INPUT_K8S_CONFIG_OPERATION_TYPE = "k8s-config-operation-type"
51
52         const val OUTPUT_STATUSES = "statuses"
53         const val OUTPUT_SKIPPED = "skipped"
54         const val OUTPUT_UPLOADED = "uploaded"
55         const val OUTPUT_ERROR = "error"
56     }
57
58     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
59         log.info("Triggering K8s Config Value component logic.")
60         val inputParameterNames = arrayOf(
61             INPUT_K8S_RB_CONFIG_TEMPLATE_NAME,
62             INPUT_K8S_RB_CONFIG_NAME,
63             INPUT_K8S_RB_CONFIG_VERSION,
64             INPUT_K8S_INSTANCE_ID,
65             INPUT_K8S_CONFIG_OPERATION_TYPE,
66             INPUT_K8S_CONFIG_VALUE_SOURCE,
67             INPUT_ARTIFACT_PREFIX_NAMES
68         )
69         val outputPrefixStatuses = mutableMapOf<String, String>()
70         val inputParamsMap = mutableMapOf<String, JsonNode?>()
71
72         inputParameterNames.forEach {
73             inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
74         }
75
76         log.info("Getting the template prefixes")
77         val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
78
79         log.info("Iterating over prefixes in resource assignment map.")
80         for (prefix in prefixList) {
81             outputPrefixStatuses[prefix] = OUTPUT_SKIPPED
82             val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
83             val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode
84             val prefixInputParamsMap = inputParamsMap.toMutableMap()
85             prefixInputParamsMap.forEach { (inputParamName, value) ->
86                 if (value == null) {
87                     val mapValue = assignmentMapPrefix.get(inputParamName)
88                     log.debug("$inputParamName value was $value so we fetch $mapValue")
89                     prefixInputParamsMap[inputParamName] = mapValue
90                 }
91             }
92
93             val templateName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_TEMPLATE_NAME]?.returnNullIfMissing()?.asText()
94             val configName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_NAME]?.returnNullIfMissing()?.asText()
95             val instanceId: String? = prefixInputParamsMap[INPUT_K8S_INSTANCE_ID]?.returnNullIfMissing()?.asText()
96             var valueSource: String? = prefixInputParamsMap[INPUT_K8S_CONFIG_VALUE_SOURCE]?.returnNullIfMissing()?.asText()
97             var configVersion: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_VERSION]?.returnNullIfMissing()?.asText()
98             val operationType = prefixInputParamsMap[INPUT_K8S_CONFIG_OPERATION_TYPE]?.returnNullIfMissing()?.asText()?.toUpperCase()
99
100             if (valueSource == null) {
101                 valueSource = configName
102                 log.info("Config name used instead of value source")
103             }
104             when (operationType) {
105                 null, OperationType.CREATE.toString() -> createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
106                 OperationType.UPDATE.toString() -> updateOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
107                 OperationType.DELETE.toString() -> deleteOperation(instanceId, configName, false)
108                 OperationType.DELETE_CONFIG.toString() -> deleteOperation(instanceId, configName, true)
109                 OperationType.ROLLBACK.toString() -> rollbackOperation(instanceId, configName, configVersion)
110                 else -> throw BluePrintProcessorException("Unknown operation type: $operationType")
111             }
112         }
113     }
114
115     private suspend fun createOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) {
116         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
117         if (templateName == null || configName == null || instanceId == null || valueSource == null) {
118             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_CONFIG_VALUE_SOURCE or $INPUT_K8S_RB_CONFIG_NAME is null - skipping create")
119         } else if (templateName.isEmpty()) {
120             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping create")
121         } else if (configName.isEmpty()) {
122             log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping create")
123         } else if (api.hasConfigurationValues(instanceId, configName)) {
124             log.info("Configuration already exists - skipping create")
125         } else {
126             log.info("Uploading K8s config..")
127             outputPrefixStatuses[prefix] = OUTPUT_ERROR
128             val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
129             val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource)
130             if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
131                 throw BluePrintProcessorException(
132                     "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type"
133                 )
134             val configValueRequest = K8sConfigValueRequest()
135             configValueRequest.templateName = templateName
136             configValueRequest.configName = configName
137             configValueRequest.description = valueSource
138             configValueRequest.values = parseResult(valueSource, artifact.file)
139             api.createConfigurationValues(configValueRequest, instanceId)
140         }
141     }
142
143     private suspend fun updateOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) {
144         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
145         if (templateName == null || configName == null || instanceId == null || valueSource == null) {
146             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_CONFIG_VALUE_SOURCE or $INPUT_K8S_RB_CONFIG_NAME is null - skipping update")
147         } else if (templateName.isEmpty()) {
148             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping update")
149         } else if (configName.isEmpty()) {
150             log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping update")
151         } else if (!api.hasConfigurationValues(instanceId, configName)) {
152             log.info("Configuration does not exist - doing create instead")
153             createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
154         } else {
155             log.info("Updating K8s config..")
156             outputPrefixStatuses[prefix] = OUTPUT_ERROR
157             val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
158             val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource)
159             if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
160                 throw BluePrintProcessorException(
161                     "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type"
162                 )
163             if (api.hasConfigurationValues(instanceId, configName)) {
164                 val configValueRequest = K8sConfigValueRequest()
165                 configValueRequest.templateName = templateName
166                 configValueRequest.configName = configName
167                 configValueRequest.description = valueSource
168                 configValueRequest.values = parseResult(valueSource, artifact.file)
169                 api.editConfigurationValues(configValueRequest, instanceId, configName)
170             } else {
171                 throw BluePrintProcessorException("Error while getting configuration value")
172             }
173         }
174     }
175
176     private fun rollbackOperation(instanceId: String?, configName: String?, configVersion: String?) {
177         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
178         if (instanceId == null || configName == null || configVersion == null) {
179             log.warn("$INPUT_K8S_INSTANCE_ID or $INPUT_K8S_RB_CONFIG_NAME or $INPUT_K8S_RB_CONFIG_VERSION is null - skipping delete")
180         } else {
181             if (api.hasConfigurationValues(instanceId, configName))
182                 api.rollbackConfigurationValues(instanceId, configName, configVersion, null)
183             else {
184                 throw BluePrintProcessorException(
185                     "Configuration $configName does not exist. Cannot perform delete operation"
186                 )
187             }
188         }
189     }
190
191     private fun deleteOperation(instanceId: String?, configName: String?, onlyDeleteConfig: Boolean) {
192         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
193         if (instanceId == null || configName == null) {
194             log.warn("$INPUT_K8S_INSTANCE_ID or $INPUT_K8S_RB_CONFIG_NAME is null - skipping delete")
195         } else {
196             if (api.hasConfigurationValues(instanceId, configName)) {
197                 if (onlyDeleteConfig)
198                     api.deleteConfigurationValues(instanceId, configName, true)
199                 else
200                     api.editConfigurationValuesByDelete(instanceId, configName)
201             } else {
202                 if (onlyDeleteConfig)
203                     log.info("Configuration does not exists - skipping delete")
204                 else
205                     throw BluePrintProcessorException(
206                         "Configuration $configName does not exist. Cannot perform delete operation"
207                     )
208             }
209         }
210     }
211
212     private suspend fun parseResult(templateValueSource: String, k8sConfigLocation: String): Any {
213         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
214         val bluePrintBasePath: String = bluePrintContext.rootPath
215         val configeValueSourceFilePath: Path = Paths.get(
216             bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation)
217         )
218
219         if (!configeValueSourceFilePath.toFile().exists() || configeValueSourceFilePath.toFile().isDirectory)
220             throw BluePrintProcessorException("Specified config value source $k8sConfigLocation is not a file")
221
222         var obj: Any? = null
223         val yamlReader = ObjectMapper(YAMLFactory())
224         if (configeValueSourceFilePath.toFile().extension.toLowerCase() == "vtl") {
225             log.info("Config building started from source $templateValueSource")
226             val properties: MutableMap<String, Any> = mutableMapOf()
227             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
228             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
229             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
230             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
231             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
232             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
233             val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources(
234                 bluePrintRuntimeService,
235                 nodeTemplateName,
236                 templateValueSource,
237                 properties
238             )
239
240             val resolvedJsonContent = resolutionResult.second
241                 .associateBy({ it.name }, { it.property?.value })
242                 .asJsonNode()
243
244             val newContent: String = templateValues(configeValueSourceFilePath.toFile(), resolvedJsonContent)
245             obj = yamlReader.readValue(newContent, Any::class.java)
246         } else {
247             val ymlSourceFile = getYmlSourceFile(k8sConfigLocation)
248             obj = yamlReader.readValue(ymlSourceFile, Any::class.java)
249         }
250         val jsonWriter = ObjectMapper()
251         return jsonWriter.convertValue(obj)
252     }
253
254     private fun templateValues(templateFile: File, params: JsonNode): String {
255         val fileContent = templateFile.bufferedReader().readText()
256         return BluePrintVelocityTemplateService.generateContent(
257             fileContent,
258             params.toString(), true
259         )
260     }
261
262     private fun getYmlSourceFile(templateValueSource: String): File {
263         val bluePrintBasePath: String = bluePrintRuntimeService.bluePrintContext().rootPath
264         val profileSourceFileFolderPath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus(templateValueSource))
265
266         if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory)
267             return profileSourceFileFolderPath.toFile()
268         else
269             throw BluePrintProcessorException("Template value $profileSourceFileFolderPath is missing in CBA folder")
270     }
271
272     private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
273         val result = ArrayList<String>()
274         when (node) {
275             is ArrayNode -> {
276                 val arrayNode = node.toList()
277                 for (prefixNode in arrayNode)
278                     result.add(prefixNode.asText())
279             }
280             is ObjectNode -> {
281                 result.add(node.asText())
282             }
283         }
284         return result
285     }
286
287     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
288         addError(runtimeException.message!!)
289     }
290
291     private enum class OperationType {
292         CREATE, UPDATE, DELETE, ROLLBACK, DELETE_CONFIG
293     }
294 }