diff options
24 files changed, 574 insertions, 82 deletions
diff --git a/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sconfig-content.json b/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sconfig-content.json new file mode 100644 index 000000000..d001d05af --- /dev/null +++ b/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sconfig-content.json @@ -0,0 +1,5 @@ +{ + "description": "K8s Config Folder Artifact", + "version": "1.0.0", + "derived_from": "tosca.artifacts.Implementation" +}
\ No newline at end of file diff --git a/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sprofile-content.json b/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sprofile-content.json new file mode 100644 index 000000000..8a68dfa42 --- /dev/null +++ b/components/model-catalog/definition-type/starter-type/artifact_type/artifact-k8sprofile-content.json @@ -0,0 +1,5 @@ +{ + "description": "K8s Profile Folder Artifact", + "version": "1.0.0", + "derived_from": "tosca.artifacts.Implementation" +}
\ No newline at end of file diff --git a/components/model-catalog/definition-type/starter-type/node_type/component-k8s-config-value.json b/components/model-catalog/definition-type/starter-type/node_type/component-k8s-config-value.json index 901f322d0..2249d16d2 100644 --- a/components/model-catalog/definition-type/starter-type/node_type/component-k8s-config-value.json +++ b/components/model-catalog/definition-type/starter-type/node_type/component-k8s-config-value.json @@ -17,12 +17,12 @@ "operations": { "process": { "inputs": { - "k8s-template-name": { + "k8s-rb-config-template-name": { "description": "K8s template name", "required": false, "type": "string" }, - "k8s-config-name": { + "k8s-rb-config-name": { "description": "K8s config name", "required": false, "type": "string" @@ -32,18 +32,24 @@ "required": false, "type": "string" }, - "k8s-rb-template-value-source": { + "k8s-rb-config-value-source": { "description": "Location of value source in CBA", "required": false, "type": "string" }, - "k8s-operation-type" : { - "required" : false, - "type" : "string", - "constraints" : [{ - "valid_values" : [ "create", "update", "rollback" ] - }], - "default" : "create" + "k8s-config-operation-type": { + "required": false, + "type": "string", + "constraints": [ + { + "valid_values": [ + "create", + "update", + "delete" + ] + } + ], + "default": "create" }, "artifact-prefix-names": { "description": "Resource Assignment Artifact Prefix names", diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-name.json b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-name.json new file mode 100644 index 000000000..8fb643b7d --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-name.json @@ -0,0 +1,19 @@ +{ + "tags": "k8s, cnf, k8s-rb-definition-name", + "name": "k8s-rb-definition-name", + "group": "default", + "property": { + "description": "K8s RB definition name. Associated with vf-module-model-invariant-uuid", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-version.json b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-version.json new file mode 100644 index 000000000..633b412c6 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-definition-version.json @@ -0,0 +1,19 @@ +{ + "tags": "k8s, cnf, k8s-rb-definition-version", + "name": "k8s-rb-definition-version", + "group": "default", + "property": { + "description": "K8s RB definition version. Associated with vf-module-model-version", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-name.json b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-name.json new file mode 100644 index 000000000..b220329bb --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-name.json @@ -0,0 +1,19 @@ +{ + "tags": "k8s, cnf, k8s-rb-profile-name", + "name": "k8s-rb-profile-name", + "group": "default", + "property": { + "description": "Profile name used in multicloud/k8s plugin to identify Helm chart(s) where this mapping is providing override values.", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-namespace.json b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-namespace.json new file mode 100644 index 000000000..a7cd5c806 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-namespace.json @@ -0,0 +1,39 @@ +{ + "tags": "k8s, cnf, k8s-rb-profile-namespace", + "name": "k8s-rb-profile-namespace", + "group": "default", + "property": { + "description": "Profile name used in multicloud/k8s plugin", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + }, + "sdnc": { + "type": "source-rest", + "properties": { + "verb": "GET", + "type": "JSON", + "url-path": "/restconf/config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/k8s-rb-profile-namespace", + "path": "/param/0/value", + "input-key-mapping": { + "service-instance-id": "service-instance-id", + "vnf-id": "vnf-id" + }, + "output-key-mapping": { + "k8s-rb-profile-namespace": "value" + }, + "key-dependencies": [ + "service-instance-id", + "vnf-id" + ] + } + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-source.json b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-source.json new file mode 100644 index 000000000..4ed85665c --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/k8s-rb-profile-source.json @@ -0,0 +1,19 @@ +{ + "tags": "k8s, cnf, k8s-rb-profile-source", + "name": "k8s-rb-profile-source", + "group": "default", + "property": { + "description": "The source folder or file relative to 'Templates/k8s-profiles' folder", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-invariant-uuid.json b/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-invariant-uuid.json new file mode 100644 index 000000000..6fa3c7769 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-invariant-uuid.json @@ -0,0 +1,35 @@ +{ + "tags": "vnf, vf-module, vf-module-model-invariant-uuid", + "name": "vf-module-model-invariant-uuid", + "group": "default", + "property": { + "description": "vf-module-model-invariant-uuid", + "type": "string" + }, + "updated-by": "MALAKOV, YURIY <yuriy.malakov@att.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + }, + "processor-db": { + "type": "source-db", + "properties": { + "type": "SQL", + "query": "select sdnctl.VF_MODULE_MODEL.invariant_uuid as vf_module_model_invariant_uuid from sdnctl.VF_MODULE_MODEL where sdnctl.VF_MODULE_MODEL.customization_uuid=:customizationid", + "input-key-mapping": { + "customizationid": "vf-module-model-customization-uuid" + }, + "output-key-mapping": { + "vf-module-model-invariant-uuid": "vf_module_model_invariant_uuid" + }, + "key-dependencies": [ + "vf-module-model-customization-uuid" + ] + } + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-version.json b/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-version.json new file mode 100644 index 000000000..dc08ea087 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vf-module-model-version.json @@ -0,0 +1,35 @@ +{ + "tags": "vnf, vf-module, vf-module-model-version", + "name": "vf-module-model-version", + "group": "default", + "property": { + "description": "vf-module-model-version", + "type": "string" + }, + "updated-by": "MALAKOV, YURIY <yuriy.malakov@att.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + }, + "processor-db": { + "type": "source-db", + "properties": { + "type": "SQL", + "query": "select sdnctl.VF_MODULE_MODEL.uuid as vf_module_model_version from sdnctl.VF_MODULE_MODEL where sdnctl.VF_MODULE_MODEL.customization_uuid=:customizationid", + "input-key-mapping": { + "customizationid": "vf-module-model-customization-uuid" + }, + "output-key-mapping": { + "vf-module-model-version": "vf_module_model_version" + }, + "key-dependencies": [ + "vf-module-model-customization-uuid" + ] + } + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vf-modules-list.json b/components/model-catalog/resource-dictionary/starter-dictionary/vf-modules-list.json new file mode 100644 index 000000000..0bf66f6e6 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vf-modules-list.json @@ -0,0 +1,37 @@ +{ + "tags": "vnf, vf-modules-list", + "name": "vf-modules-list", + "group": "default", + "property": { + "description": "List of vf-modules associated with vnf", + "type": "json" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + }, + "aai-data": { + "type": "source-rest", + "properties": { + "verb": "GET", + "type": "JSON", + "url-path": "/aai/v19/network/generic-vnfs/generic-vnf/${vnf-id}?depth=1", + "path": "/vf-modules", + "input-key-mapping": { + "vnf-id": "vnf-id" + }, + "output-key-mapping": { + "vf-modules": "vf-module" + }, + "key-dependencies": [ + "vnf-id" + ] + } + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vpg_management_port.json b/components/model-catalog/resource-dictionary/starter-dictionary/vpg_management_port.json new file mode 100644 index 000000000..20003dfd6 --- /dev/null +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vpg_management_port.json @@ -0,0 +1,19 @@ +{ + "tags": "k8s, cnf, vpg-management-port", + "name": "vpg-management-port", + "group": "default", + "property": { + "description": "Ssh port number in k8s nodeport to associate with vpg", + "type": "string" + }, + "updated-by": "Lukasz Rajewski <lukasz.rajewski@orange.com>", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/K8sPluginDefinitionApi.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/K8sPluginDefinitionApi.kt index 05c3021d9..ae2d1f634 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/K8sPluginDefinitionApi.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/K8sPluginDefinitionApi.kt @@ -111,16 +111,18 @@ class K8sPluginDefinitionApi( "/profile/${profile.profileName}/content", filePath ) + log.debug(result.toString()) if (result.status !in 200..299) { throw Exception(result.body) } + log.debug("Profile uploaded properly") } catch (e: Exception) { log.error("Caught exception trying to upload k8s rb profile ${profile.profileName}") throw BlueprintProcessorException("${e.message}") } } - fun createTemplate(definition: String, definitionVersion: String, template: K8sTemplate): Boolean { + fun createTemplate(definition: String, definitionVersion: String, template: K8sTemplate) { val rbDefinitionService = K8sDefinitionRestClient(k8sConfiguration, definition, definitionVersion) val templateJsonString: String = objectMapper.writeValueAsString(template) try { @@ -130,20 +132,23 @@ class K8sPluginDefinitionApi( templateJsonString ) log.debug(result.toString()) - return result.status in 200..299 + if (result.status !in 200..299) { + throw Exception(result.body) + } } catch (e: Exception) { log.error("Caught exception during create template") throw BlueprintProcessorException("${e.message}") } } - fun uploadTemplate(definition: String, definitionVersion: String, template: K8sTemplate, filePath: Path) { + fun uploadConfigTemplateContent(definition: String, definitionVersion: String, template: K8sTemplate, filePath: Path) { val fileUploadService = K8sUploadFileRestClientService(k8sConfiguration, definition, definitionVersion) try { val result: BlueprintWebClientService.WebClientResponse<String> = fileUploadService.uploadBinaryFile( "/config-template/${template.templateName}/content", filePath ) + log.debug(result.toString()) if (result.status !in 200..299) { throw Exception(result.body) } @@ -162,6 +167,9 @@ class K8sPluginDefinitionApi( "" ) log.debug(result.toString()) + if (result.status !in 200..299) { + throw Exception(result.body) + } } catch (e: Exception) { log.error("Caught exception during get template") throw BlueprintProcessorException("${e.message}") diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigTemplateComponent.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigTemplateComponent.kt index cb759e534..0944c2359 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigTemplateComponent.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigTemplateComponent.kt @@ -141,7 +141,7 @@ open class K8sConfigTemplateComponent( } val bluePrintContext = bluePrintRuntimeService.bluePrintContext() val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, templateSource) - if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE) + if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG) throw BlueprintProcessorException( "Unexpected template artifact type for template source $templateSource. Expecting: $artifact.type" ) @@ -151,7 +151,7 @@ open class K8sConfigTemplateComponent( val templateFilePath: Path = prepareTemplateFile(templateName, templateSource, artifact.file) api.createTemplate(definitionName, definitionVersion, template) - api.uploadTemplate(definitionName, definitionVersion, template, templateFilePath) + api.uploadConfigTemplateContent(definitionName, definitionVersion, template, templateFilePath) log.info("K8s Config Upload Completed") outputPrefixStatuses[prefix] = OUTPUT_UPLOADED diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt index 2e7b34e01..34b6ea187 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt @@ -11,12 +11,17 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInpu import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sConfigValueRequest import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sPluginInstanceApi +import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants +import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException +import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing +import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintVelocityTemplateService import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory import org.springframework.context.annotation.Scope @@ -28,7 +33,8 @@ import java.nio.file.Paths @Component("component-k8s-config-value") @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) open class K8sConfigValueComponent( - private var bluePrintPropertiesService: BlueprintPropertiesService + private var bluePrintPropertiesService: BlueprintPropertiesService, + private val resourceResolutionService: ResourceResolutionService ) : AbstractComponentFunction() { private val log = LoggerFactory.getLogger(K8sConfigValueComponent::class.java)!! @@ -36,11 +42,11 @@ open class K8sConfigValueComponent( companion object { const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map" const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names" - const val INPUT_K8S_TEMPLATE_NAME = "k8s-template-name" - const val INPUT_K8S_CONFIG_NAME = "k8s-config-name" + const val INPUT_K8S_RB_CONFIG_TEMPLATE_NAME = "k8s-rb-config-template-name" + const val INPUT_K8S_RB_CONFIG_NAME = "k8s-rb-config-name" const val INPUT_K8S_INSTANCE_ID = "k8s-instance-id" - const val INPUT_K8S_TEMPLATE_VALUE_SOURCE = "k8s-rb-template-value-source" - const val INPUT_K8S_OPERATION_TYPE = "k8s-operation-type" + const val INPUT_K8S_CONFIG_VALUE_SOURCE = "k8s-rb-config-value-source" + const val INPUT_K8S_CONFIG_OPERATION_TYPE = "k8s-config-operation-type" const val OUTPUT_STATUSES = "statuses" const val OUTPUT_SKIPPED = "skipped" @@ -51,11 +57,11 @@ open class K8sConfigValueComponent( override suspend fun processNB(executionRequest: ExecutionServiceInput) { log.info("Triggering K8s Config Value component logic.") val inputParameterNames = arrayOf( - INPUT_K8S_TEMPLATE_NAME, - INPUT_K8S_CONFIG_NAME, + INPUT_K8S_RB_CONFIG_TEMPLATE_NAME, + INPUT_K8S_RB_CONFIG_NAME, INPUT_K8S_INSTANCE_ID, - INPUT_K8S_OPERATION_TYPE, - INPUT_K8S_TEMPLATE_VALUE_SOURCE, + INPUT_K8S_CONFIG_OPERATION_TYPE, + INPUT_K8S_CONFIG_VALUE_SOURCE, INPUT_ARTIFACT_PREFIX_NAMES ) val outputPrefixStatuses = mutableMapOf<String, String>() @@ -82,74 +88,81 @@ open class K8sConfigValueComponent( } } - val templateName: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_NAME]?.returnNullIfMissing()?.asText() - val configName: String? = prefixInputParamsMap[INPUT_K8S_CONFIG_NAME]?.returnNullIfMissing()?.asText() + val templateName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_TEMPLATE_NAME]?.returnNullIfMissing()?.asText() + val configName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_NAME]?.returnNullIfMissing()?.asText() val instanceId: String? = prefixInputParamsMap[INPUT_K8S_INSTANCE_ID]?.returnNullIfMissing()?.asText() - val valueSource: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_VALUE_SOURCE]?.returnNullIfMissing()?.asText() - val operationType = prefixInputParamsMap[INPUT_K8S_TEMPLATE_VALUE_SOURCE]?.returnNullIfMissing()?.asText()?.toUpperCase() + var valueSource: String? = prefixInputParamsMap[INPUT_K8S_CONFIG_VALUE_SOURCE]?.returnNullIfMissing()?.asText() + val operationType = prefixInputParamsMap[INPUT_K8S_CONFIG_OPERATION_TYPE]?.returnNullIfMissing()?.asText()?.toUpperCase() + if (valueSource == null) { + valueSource = configName + log.info("Config name used instead of value source") + } if (operationType == null || operationType == OperationType.CREATE.toString()) createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName) else if (operationType == OperationType.UPDATE.toString()) updateOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName) - else if (operationType == OperationType.ROLLBACK.toString()) - rollbackOperation(instanceId) + else if (operationType == OperationType.DELETE.toString()) + deleteOperation(instanceId, configName) else throw BlueprintProcessorException("Unknown operation type: $operationType") } } - private fun createOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) { + private suspend fun createOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) { + val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) if (templateName == null || configName == null || instanceId == null || valueSource == null) { - log.warn("$INPUT_K8S_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_TEMPLATE_VALUE_SOURCE or $INPUT_K8S_CONFIG_NAME is null") + 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") } else if (templateName.isEmpty()) { - log.warn("$INPUT_K8S_TEMPLATE_NAME is empty") + log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping create") } else if (configName.isEmpty()) { - log.warn("$INPUT_K8S_CONFIG_NAME is empty") + log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping create") + } else if (api.hasConfigurationValues(instanceId, configName)) { + log.info("Configuration already exists - skipping create") } else { - log.info("Uploading K8s template value..") + log.info("Uploading K8s config..") outputPrefixStatuses[prefix] = OUTPUT_ERROR val bluePrintContext = bluePrintRuntimeService.bluePrintContext() val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource) - if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE) + if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG) throw BlueprintProcessorException( - "Unexpected profile artifact type for profile source $valueSource. Expecting: $artifact.type" + "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type" ) - // Creating API connector - val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) val configValueRequest = K8sConfigValueRequest() configValueRequest.templateName = templateName configValueRequest.configName = configName configValueRequest.description = valueSource - configValueRequest.values = parseResult(valueSource) + configValueRequest.values = parseResult(valueSource, artifact.file) api.createConfigurationValues(configValueRequest, instanceId) } } - private fun updateOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) { + private suspend fun updateOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) { + val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) if (templateName == null || configName == null || instanceId == null || valueSource == null) { - log.warn("$INPUT_K8S_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_TEMPLATE_VALUE_SOURCE or $INPUT_K8S_CONFIG_NAME is null") + 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") } else if (templateName.isEmpty()) { - log.warn("$INPUT_K8S_TEMPLATE_NAME is empty") + log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping update") } else if (configName.isEmpty()) { - log.warn("$INPUT_K8S_CONFIG_NAME is empty") + log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping update") + } else if (!api.hasConfigurationValues(instanceId, configName)) { + log.info("Configuration does not exist - doing create instead") + createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName) } else { - log.info("Uploading K8s template value..") + log.info("Updating K8s config..") outputPrefixStatuses[prefix] = OUTPUT_ERROR val bluePrintContext = bluePrintRuntimeService.bluePrintContext() val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource) - if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE) + if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG) throw BlueprintProcessorException( - "Unexpected profile artifact type for profile source $valueSource. Expecting: $artifact.type" + "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type" ) - // Creating API connector - val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) if (api.hasConfigurationValues(instanceId, configName)) { val configValueRequest = K8sConfigValueRequest() configValueRequest.templateName = templateName configValueRequest.configName = configName configValueRequest.description = valueSource - configValueRequest.values = parseResult(valueSource) + configValueRequest.values = parseResult(valueSource, artifact.file) api.editConfigurationValues(configValueRequest, instanceId, configName) } else { throw BlueprintProcessorException("Error while getting configuration value") @@ -157,24 +170,67 @@ open class K8sConfigValueComponent( } } - private fun rollbackOperation(instanceId: String?) { - if (instanceId != null) { - val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) - api.rollbackConfigurationValues(instanceId) + private fun deleteOperation(instanceId: String?, configName: String?) { + val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService)) + if (instanceId == null || configName == null) { + log.warn("$INPUT_K8S_INSTANCE_ID or $INPUT_K8S_RB_CONFIG_NAME is null - skipping delete") + } else if (api.hasConfigurationValues(instanceId, configName)) { + log.info("Configuration does not exists - skipping delete") } else { - throw BlueprintProcessorException("$INPUT_K8S_INSTANCE_ID is null") + api.deleteConfigurationValues(instanceId, configName) } } - private fun parseResult(templateValueSource: String): Any { - val ymlSourceFile = getYmlSourceFile(templateValueSource) + private suspend fun parseResult(templateValueSource: String, k8sConfigLocation: String): Any { + val bluePrintContext = bluePrintRuntimeService.bluePrintContext() + val bluePrintBasePath: String = bluePrintContext.rootPath + val configeValueSourceFilePath: Path = Paths.get( + bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation) + ) + + if (!configeValueSourceFilePath.toFile().exists() || configeValueSourceFilePath.toFile().isDirectory) + throw BlueprintProcessorException("Specified config value source $k8sConfigLocation is not a file") + + var obj: Any? = null val yamlReader = ObjectMapper(YAMLFactory()) - val obj: Any = yamlReader.readValue(ymlSourceFile, Any::class.java) + if (configeValueSourceFilePath.toFile().extension.toLowerCase() == "vtl") { + log.info("Config building started from source $templateValueSource") + val properties: MutableMap<String, Any> = mutableMapOf() + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = "" + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = "" + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = "" + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1 + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false + val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources( + bluePrintRuntimeService, + nodeTemplateName, + templateValueSource, + properties + ) + + val resolvedJsonContent = resolutionResult.second + .associateBy({ it.name }, { it.property?.value }) + .asJsonNode() + val newContent: String = templateValues(configeValueSourceFilePath.toFile(), resolvedJsonContent) + obj = yamlReader.readValue(newContent, Any::class.java) + } else { + val ymlSourceFile = getYmlSourceFile(k8sConfigLocation) + obj = yamlReader.readValue(ymlSourceFile, Any::class.java) + } val jsonWriter = ObjectMapper() return jsonWriter.convertValue(obj) } + private fun templateValues(templateFile: File, params: JsonNode): String { + val fileContent = templateFile.bufferedReader().readText() + return BlueprintVelocityTemplateService.generateContent( + fileContent, + params, true + ) + } + private fun getYmlSourceFile(templateValueSource: String): File { val bluePrintBasePath: String = bluePrintRuntimeService.bluePrintContext().rootPath val profileSourceFileFolderPath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus(templateValueSource)) @@ -205,6 +261,6 @@ open class K8sConfigValueComponent( } private enum class OperationType { - CREATE, UPDATE, ROLLBACK + CREATE, UPDATE, DELETE } } diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt index 823009fbb..0106b81a4 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt @@ -1,29 +1,39 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance +import com.fasterxml.jackson.annotation.JsonAlias import com.fasterxml.jackson.annotation.JsonProperty class K8sConfigValueResponse { - @get:JsonProperty("rb-name") var rbName: String? = null @get:JsonProperty("rb-version") var rbVersion: String? = null + @get:JsonProperty("instance-id") + var instanceId: String? = null + @get:JsonProperty("profile-name") var profileName: String? = null + @get:JsonProperty("description") + var description: String? = null + @get:JsonProperty("template-name") var templateName: String? = null @get:JsonProperty("config-name") var configName: String? = null - @get:JsonProperty("config-name") - var configVersion: String? = null + @get:JsonProperty("config-version") + @get:JsonAlias("config-verion") + var configVersion: Integer? = null + + @get:JsonProperty("values") + var values: Map<String, Object>? = null override fun toString(): String { - return "$rbName:$rbVersion:$profileName:$templateName:$configName:$configVersion" + return "$templateName:$configName" } override fun equals(other: Any?): Boolean { diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sPluginInstanceApi.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sPluginInstanceApi.kt index 7663699aa..7834c050d 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sPluginInstanceApi.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sPluginInstanceApi.kt @@ -121,14 +121,24 @@ class K8sPluginInstanceApi( } } - fun queryInstanceStatus(instanceId: String, kind: String, apiVersion: String, name: String?, labels: String?): K8sRbInstanceStatus? { + fun queryInstanceStatus( + instanceId: String, + kind: String, + apiVersion: String, + name: String? = null, + labels: Map<String, String>? = null + ): K8sRbInstanceStatus? { val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) try { var path: String = "/query?ApiVersion=$apiVersion&Kind=$kind" if (name != null) path = path.plus("&Name=$name") - if (labels != null) - path = path.plus("&Labels=$labels") + if (labels != null && labels.isNotEmpty()) { + path = path.plus("&Labels=") + for ((name, value) in labels) + path = path.plus("$name%3D$value,") + path = path.trimEnd(',') + } val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( GET.name, path, @@ -240,7 +250,7 @@ class K8sPluginInstanceApi( } else throw BlueprintProcessorException(result.body) } catch (e: Exception) { - log.error("Caught exception trying to get k8s rb instance") + log.error("Caught exception trying to create config instance") throw BlueprintProcessorException("${e.message}") } } @@ -262,7 +272,7 @@ class K8sPluginInstanceApi( } else throw BlueprintProcessorException(result.body) } catch (e: Exception) { - log.error("Caught exception trying to get k8s rb instance") + log.error("Caught exception trying to edit config instance") throw BlueprintProcessorException("${e.message}") } } @@ -283,6 +293,45 @@ class K8sPluginInstanceApi( } } + fun getConfigurationValues(instanceId: String, configName: String): K8sConfigValueResponse? { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + GET.name, + "/config/$configName", + "" + ) + log.debug(result.toString()) + return if (result.status in 200..299) { + val parsedObject: K8sConfigValueResponse? = JacksonUtils.readValue( + result.body, K8sConfigValueResponse::class.java + ) + parsedObject + } else + throw BlueprintProcessorException(result.body) + } catch (e: Exception) { + log.error("Caught exception trying to get config instance") + throw BlueprintProcessorException("${e.message}") + } + } + + fun deleteConfigurationValues(instanceId: String, configName: String) { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + DELETE.name, + "/config/$configName", + "" + ) + log.debug(result.toString()) + if (result.status !in 200..299) + throw BlueprintProcessorException(result.body) + } catch (e: Exception) { + log.error("Caught exception trying to delete config instance") + throw BlueprintProcessorException("${e.message}") + } + } + fun rollbackConfigurationValues(instanceId: String): K8sConfigValueResponse? { val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) try { @@ -305,7 +354,7 @@ class K8sPluginInstanceApi( } } - fun createConfigurationValues(instanceId: String): K8sConfigValueResponse? { + fun tagConfigurationValues(instanceId: String): K8sConfigValueResponse? { val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) try { val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BlueprintConstants.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BlueprintConstants.kt index cfe436023..256339c62 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BlueprintConstants.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BlueprintConstants.kt @@ -232,6 +232,7 @@ object BlueprintConstants { const val MODEL_TYPE_ARTIFACT_DIRECTED_GRAPH = "artifact-directed-graph" const val MODEL_TYPE_ARTIFACT_COMPONENT_JAR = "artifact-component-jar" const val MODEL_TYPE_ARTIFACT_K8S_PROFILE = "artifact-k8sprofile-content" + const val MODEL_TYPE_ARTIFACT_K8S_CONFIG = "artifact-k8sconfig-content" const val TOSCA_SPEC = "TOSCA" diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctions.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctions.kt index 5995a8a9e..26f74669e 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctions.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctions.kt @@ -33,7 +33,7 @@ fun String.toGraph(): Graph { if (!startsWith('[') || !endsWith(']')) { throw IllegalArgumentException("Expected string starting '[' and ending with ']' but it was '$") } - val tokens = substring(1, length - 1).split(", ").map { it.split(graphTokenSeparators) } + val tokens = substring(1, length - 1).replace("\n", "").split(", ").map { it.trim().split(graphTokenSeparators) } val nodes = tokens.flatMap { it.take(2) }.toCollection(LinkedHashSet()) val edges = tokens.filter { it.size == 3 }.map { Graph.TermForm.Term(it[0], it[1], EdgeLabel.valueOf(it[2])) } return Graph.labeledDirectedTerms(Graph.TermForm(nodes, edges)) @@ -41,7 +41,7 @@ fun String.toGraph(): Graph { fun Graph.toAdjacencyList(): Graph.AdjacencyList<String, EdgeLabel> { val entries = nodes.values.map { node -> - val links = node.edges.map { Graph.AdjacencyList.Link(it.target(node).id, it.label) } + val links = node.edges.map { Graph.AdjacencyList.Link(it.target.id, it.label) } Graph.AdjacencyList.Entry(node = node.id, links = links) } return Graph.AdjacencyList(entries) @@ -54,14 +54,33 @@ fun Graph.findAllPaths(from: String, to: String, path: List<String> = emptyList( .flatMap { findAllPaths(it.id, to, path + from) } } -fun Graph.findCycles(node: String): List<List<String>> { - fun findCycles(path: List<String>): List<List<String>> { - if (path.size > 3 && path.first() == path.last()) return listOf(path) - return nodes[path.last()]!!.neighbors() - .filterNot { path.tail().contains(it.id) } - .flatMap { findCycles(path + it.id) } +fun Graph.isAcyclic(): Boolean { + val startNodes = startNodes() + if (startNodes.isEmpty()) + return false + + val adj: Map<String, Set<String>> = toAdjacencyList().entries + .associate { it.node to it.links } + .mapValues { it.value.map { x -> x.node }.toSet() } + + fun hasCycle(node: String, visited: MutableSet<String> = mutableSetOf()): Boolean { + if (visited.contains(node)) + return true + visited.add(node) + + if (adj[node]!!.isEmpty()) { + visited.remove(node) + return false + } + + if (adj[node]!!.any { hasCycle(it, visited) }) + return true + + visited.remove(node) + return false } - return findCycles(listOf(node)) + + return startNodes.none { n -> hasCycle(n.id) } } fun Graph.startNodes() = this.nodes.values.filter { diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/data/BlueprintGraph.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/data/BlueprintGraph.kt index b833db755..bc6cbe426 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/data/BlueprintGraph.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/data/BlueprintGraph.kt @@ -97,10 +97,10 @@ class Graph { val edges: MutableList<Edge> = ArrayList() - fun neighbors(): List<Node> = edges.map { edge -> edge.target(this) } + fun neighbors(): List<Node> = edges.map { it.target } fun neighbors(label: EdgeLabel): List<Node> = edges.filter { it.label == label } - .map { edge -> edge.target(this) } + .map { it.target } fun labelEdges(label: EdgeLabel): List<Edge> = edges.filter { it.label == label } @@ -114,8 +114,6 @@ class Graph { var status: EdgeStatus = EdgeStatus.NOT_STARTED ) { - fun target(node: Node): Node = target - fun equivalentTo(other: Edge) = (source == other.source && target == other.target) || (source == other.target && target == other.source) diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSL.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSL.kt index 503e07048..81593c935 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSL.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSL.kt @@ -327,6 +327,22 @@ fun BlueprintTypes.artifactTypeK8sProfileFolder(): ArtifactType { } } +fun ServiceTemplateBuilder.artifactTypeK8sConfigFolder() { + val artifactType = BlueprintTypes.artifactTypeK8sConfigFolder() + if (this.artifactTypes == null) this.artifactTypes = hashMapOf() + this.artifactTypes!![artifactType.id!!] = artifactType +} + +fun BlueprintTypes.artifactTypeK8sConfigFolder(): ArtifactType { + return artifactType( + id = BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG, + version = BlueprintConstants.DEFAULT_VERSION_NUMBER, + derivedFrom = BlueprintConstants.MODEL_TYPE_ARTIFACT_TYPE_IMPLEMENTATION, + description = "K8s Config Folder Artifact" + ) { + } +} + @Deprecated("CDS won't support, use implerative workflow definitions.") fun BlueprintTypes.artifactTypeDirectedGraph(): ArtifactType { return artifactType( diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctionsTest.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctionsTest.kt index 86cb473ae..ba4115f02 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctionsTest.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/GraphExtensionFunctionsTest.kt @@ -18,7 +18,9 @@ package org.onap.ccsdk.cds.controllerblueprints.core import org.junit.Test import org.onap.ccsdk.cds.controllerblueprints.core.data.EdgeLabel +import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.test.assertTrue class GraphExtensionFunctionsTest { @@ -34,4 +36,73 @@ class GraphExtensionFunctionsTest { val nodePath = graph.nodes["p"]!!.neighbors(EdgeLabel.SUCCESS) assertNotNull(nodePath, "failed to nodePath from graph for 'p' node 'SUCCESS' label") } + + @Test + fun `isAcyclic should return false`() { + assertFalse( + """[ + assign>deploy/SUCCESS, + deploy>assign/FAILURE + ]""".toGraph().isAcyclic() + ) + + assertFalse( + """[ + assign>deploy/SUCCESS, + deploy>recover/FAILURE, + recover>deploy/SUCCESS + ]""".toGraph().isAcyclic() + ) + + assertFalse( + """[ + assign>deploy/SUCCESS, + assign>recover/FAILURE, + recover>deploy/SUCCESS, + deploy>finalize/SUCCESS, + deploy>recover/FAILURE + ]""".toGraph().isAcyclic() + ) + + assertFalse( + """[ + A>B/SUCCESS, + A>C/SUCCESS, + B>E/SUCCESS, + B>D/FAILURE, + D>B/FAILURE, + C>E/SUCCESS + ]""".toGraph().isAcyclic() + ) + } + + @Test + fun `isAcyclic should return true`() { + assertTrue( + """[ + assign>deploy/SUCCESS, + deploy>recover/FAILURE + ]""".toGraph().isAcyclic() + ) + + assertTrue( + """[ + A>C/SUCCESS, + A>B/FAILURE, + C>B/SUCCESS + ]""".toGraph().isAcyclic() + ) + + assertTrue( + """[ + assign>execute1/SUCCESS, + assign>execute2/SUCCESS, + execute1>finalize/SUCCESS, + execute2>finalize/SUCCESS, + execute1>cleanup/FAILURE, + execute2>cleanup/FAILURE, + finalize>cleanup/SUCCESS + ]""".toGraph().isAcyclic() + ) + } } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSLTest.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSLTest.kt index 5143c9dc7..57f671dc4 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSLTest.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/dsl/BlueprintDSLTest.kt @@ -111,6 +111,7 @@ class BlueprintDSLTest { artifactTypeMappingResource() artifactTypeComponentJar() artifactTypeK8sProfileFolder() + artifactTypeK8sConfigFolder() relationshipTypeConnectsTo() relationshipTypeDependsOn() diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt index 2278dbfb4..561136a87 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/ImperativeWorkflowExecutionService.kt @@ -23,6 +23,7 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutp import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.Status import org.onap.ccsdk.cds.controllerblueprints.common.api.EventType import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants +import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintException import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext import org.onap.ccsdk.cds.controllerblueprints.core.asGraph @@ -30,6 +31,7 @@ import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty import org.onap.ccsdk.cds.controllerblueprints.core.data.EdgeLabel import org.onap.ccsdk.cds.controllerblueprints.core.data.Graph import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BlueprintWorkflowExecutionService +import org.onap.ccsdk.cds.controllerblueprints.core.isAcyclic import org.onap.ccsdk.cds.controllerblueprints.core.logger import org.onap.ccsdk.cds.controllerblueprints.core.service.AbstractBlueprintWorkFlowService import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintRuntimeService @@ -57,6 +59,10 @@ class ImperativeWorkflowExecutionService( val graph = bluePrintContext.workflowByName(workflowName).asGraph() + if (!graph.isAcyclic()) { + throw BlueprintException("Imperative workflow must be acyclic. Check on_success/on_failure for circular references") + } + return coroutineScope { ImperativeBlueprintWorkflowService( nodeTemplateExecutionService, |