aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/K8sPluginDefinitionApi.kt69
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfile.kt7
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfileUploadComponent.kt20
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt54
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sTemplate.kt8
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt28
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sPluginInstanceApi.kt209
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sRbInstance.kt154
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sPluginQueryApi.kt56
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sQueryRestClient.kt31
-rw-r--r--ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sResourceStatus.kt44
11 files changed, 646 insertions, 34 deletions
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 73c0e8029..02b255673 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
@@ -24,12 +24,15 @@ import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.profile.K
import com.fasterxml.jackson.module.kotlin.readValue
import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template.K8sTemplate
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sRbInstanceFull
import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
+import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
import org.slf4j.LoggerFactory
import org.springframework.http.HttpMethod.DELETE
import org.springframework.http.HttpMethod.GET
import org.springframework.http.HttpMethod.POST
+import org.springframework.http.HttpMethod.PUT
import java.nio.file.Path
class K8sPluginDefinitionApi(
@@ -78,6 +81,32 @@ class K8sPluginDefinitionApi(
}
}
+ fun getProfile(definition: String, definitionVersion: String, profileName: String): K8sProfile? {
+ val rbDefinitionService = K8sDefinitionRestClient(
+ k8sConfiguration,
+ definition,
+ definitionVersion
+ )
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbDefinitionService.exchangeResource(
+ GET.name,
+ "/profile/$profileName",
+ ""
+ )
+ log.debug(result.toString())
+ return if (result.status in 200..299) {
+ val parsedObject: K8sProfile? = JacksonUtils.readValue(result.body, K8sProfile::class.java)
+ parsedObject
+ } else if (result.status == 500 && result.body.contains("Error finding master table"))
+ null
+ else
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s rb profile")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
fun createProfile(definition: String, definitionVersion: String, profile: K8sProfile) {
val rbDefinitionService = K8sDefinitionRestClient(
k8sConfiguration,
@@ -100,6 +129,46 @@ class K8sPluginDefinitionApi(
}
}
+ fun updateProfile(profile: K8sProfile) {
+ val rbDefinitionService = K8sDefinitionRestClient(
+ k8sConfiguration,
+ profile.rbName!!,
+ profile.rbVersion!!
+ )
+ val profileJsonString: String = objectMapper.writeValueAsString(profile)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbDefinitionService.exchangeResource(
+ PUT.name,
+ "/profile/${profile.profileName}",
+ profileJsonString
+ )
+ if (result.status !in 200..299) {
+ throw Exception(result.body)
+ }
+ } catch (e: Exception) {
+ log.error("Caught exception trying to create k8s rb profile ${profile.profileName}")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
+ fun deleteProfile(definition: String, definitionVersion: String, profileName: String) {
+ val rbDefinitionService = K8sDefinitionRestClient(k8sConfiguration, definition, definitionVersion)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbDefinitionService.exchangeResource(
+ DELETE.name,
+ "/profile/${profileName}",
+ ""
+ )
+ 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}")
+ }
+ }
+
fun uploadProfileContent(definition: String, definitionVersion: String, profile: K8sProfile, filePath: Path) {
val fileUploadService = K8sUploadFileRestClientService(
k8sConfiguration,
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfile.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfile.kt
index 8caa1d1a5..ef0d6b9bf 100644
--- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfile.kt
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfile.kt
@@ -21,6 +21,7 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.profile
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sRbInstanceGvk
@JsonIgnoreProperties(ignoreUnknown = true)
class K8sProfile {
@@ -40,6 +41,12 @@ class K8sProfile {
@get:JsonProperty("namespace")
var namespace: String? = "default"
+ @get:JsonProperty("labels")
+ var labels: Map<String, String>? = null
+
+ @get:JsonProperty("extra-resource-types")
+ var extraResourceTypes: List<K8sRbInstanceGvk>? = null
+
override fun toString(): String {
return "$rbName:$rbVersion:$profileName"
}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfileUploadComponent.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfileUploadComponent.kt
index 067c75b3e..80e1cb4f1 100644
--- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfileUploadComponent.kt
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/profile/K8sProfileUploadComponent.kt
@@ -22,11 +22,15 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.profile
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
import org.apache.commons.io.FileUtils
import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.K8sPluginDefinitionApi
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sRbInstance
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sRbInstanceGvk
import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
@@ -65,6 +69,8 @@ open class K8sProfileUploadComponent(
const val INPUT_K8S_DEFINITION_NAME = "k8s-rb-definition-name"
const val INPUT_K8S_DEFINITION_VERSION = "k8s-rb-definition-version"
const val INPUT_K8S_PROFILE_NAMESPACE = "k8s-rb-profile-namespace"
+ const val INPUT_K8S_PROFILE_LABELS = "k8s-rb-profile-labels"
+ const val INPUT_K8S_PROFILE_EXTRA_TYPES = "k8s-rb-profile-extra-types"
const val INPUT_K8S_PROFILE_K8S_VERSION = "k8s-rb-profile-k8s-version"
const val INPUT_K8S_PROFILE_SOURCE = "k8s-rb-profile-source"
const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
@@ -86,6 +92,8 @@ open class K8sProfileUploadComponent(
INPUT_K8S_DEFINITION_NAME,
INPUT_K8S_DEFINITION_VERSION,
INPUT_K8S_PROFILE_NAMESPACE,
+ INPUT_K8S_PROFILE_LABELS,
+ INPUT_K8S_PROFILE_EXTRA_TYPES,
INPUT_K8S_PROFILE_K8S_VERSION,
INPUT_K8S_PROFILE_SOURCE,
INPUT_ARTIFACT_PREFIX_NAMES
@@ -143,6 +151,8 @@ open class K8sProfileUploadComponent(
val profileNamespace: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAMESPACE]?.returnNullIfMissing()?.asText()
val profileK8sVersion: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_K8S_VERSION]?.returnNullIfMissing()?.asText()
var profileSource: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_SOURCE]?.returnNullIfMissing()?.asText()
+ var profileLabels: JsonNode? = prefixInputParamsMap[INPUT_K8S_PROFILE_LABELS]?.returnNullIfMissing()
+ var profileExtraTypes: JsonNode? = prefixInputParamsMap[INPUT_K8S_PROFILE_EXTRA_TYPES]?.returnNullIfMissing()
if (profileNamespace == null)
throw BluePrintProcessorException("Profile $profileName namespace is missing")
if (profileSource == null) {
@@ -163,6 +173,16 @@ open class K8sProfileUploadComponent(
profile.namespace = profileNamespace
if (profileK8sVersion != null)
profile.kubernetesVersion = profileK8sVersion
+ if (profileLabels != null) {
+ val objectMapper = jacksonObjectMapper()
+ val labelList: HashMap<String, String> = objectMapper.readValue(profileLabels.toPrettyString())
+ profile.labels = labelList
+ }
+ if (profileExtraTypes != null) {
+ val objectMapper = jacksonObjectMapper()
+ val extraTypeList: ArrayList<K8sRbInstanceGvk> = objectMapper.readValue(profileExtraTypes.toPrettyString())
+ profile.extraResourceTypes = extraTypeList
+ }
val profileFilePath: Path = prepareProfileFile(profileName, profileSource, artifact.file)
api.createProfile(definitionName, definitionVersion, profile)
api.uploadProfileContent(definitionName, definitionVersion, profile, profileFilePath)
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 f1affacf4..0b6294936 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
@@ -44,6 +44,7 @@ open class K8sConfigValueComponent(
const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"
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_RB_CONFIG_VERSION = "k8s-rb-config-version"
const val INPUT_K8S_INSTANCE_ID = "k8s-instance-id"
const val INPUT_K8S_CONFIG_VALUE_SOURCE = "k8s-rb-config-value-source"
const val INPUT_K8S_CONFIG_OPERATION_TYPE = "k8s-config-operation-type"
@@ -59,6 +60,7 @@ open class K8sConfigValueComponent(
val inputParameterNames = arrayOf(
INPUT_K8S_RB_CONFIG_TEMPLATE_NAME,
INPUT_K8S_RB_CONFIG_NAME,
+ INPUT_K8S_RB_CONFIG_VERSION,
INPUT_K8S_INSTANCE_ID,
INPUT_K8S_CONFIG_OPERATION_TYPE,
INPUT_K8S_CONFIG_VALUE_SOURCE,
@@ -92,20 +94,21 @@ open class K8sConfigValueComponent(
val configName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_NAME]?.returnNullIfMissing()?.asText()
val instanceId: String? = prefixInputParamsMap[INPUT_K8S_INSTANCE_ID]?.returnNullIfMissing()?.asText()
var valueSource: String? = prefixInputParamsMap[INPUT_K8S_CONFIG_VALUE_SOURCE]?.returnNullIfMissing()?.asText()
+ var configVersion: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_VERSION]?.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.DELETE.toString())
- deleteOperation(instanceId, configName)
- else
- throw BluePrintProcessorException("Unknown operation type: $operationType")
+ when (operationType) {
+ null, OperationType.CREATE.toString() -> createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
+ OperationType.UPDATE.toString() -> updateOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
+ OperationType.DELETE.toString() -> deleteOperation(instanceId, configName, false)
+ OperationType.DELETE_CONFIG.toString() -> deleteOperation(instanceId, configName, true)
+ OperationType.ROLLBACK.toString() -> rollbackOperation(instanceId, configName, configVersion)
+ else -> throw BluePrintProcessorException("Unknown operation type: $operationType")
+ }
}
}
@@ -170,14 +173,39 @@ open class K8sConfigValueComponent(
}
}
- private fun deleteOperation(instanceId: String?, configName: String?) {
+ private fun rollbackOperation(instanceId: String?, configName: String?, configVersion: String?) {
+ val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
+ if (instanceId == null || configName == null || configVersion == null) {
+ log.warn("$INPUT_K8S_INSTANCE_ID or $INPUT_K8S_RB_CONFIG_NAME or $INPUT_K8S_RB_CONFIG_VERSION is null - skipping delete")
+ } else {
+ if (api.hasConfigurationValues(instanceId, configName))
+ api.rollbackConfigurationValues(instanceId, configName, configVersion, null)
+ else {
+ throw BluePrintProcessorException(
+ "Configuration $configName does not exist. Cannot perform delete operation"
+ )
+ }
+ }
+ }
+
+ private fun deleteOperation(instanceId: String?, configName: String?, onlyDeleteConfig: Boolean) {
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 {
- api.deleteConfigurationValues(instanceId, configName)
+ if (api.hasConfigurationValues(instanceId, configName)) {
+ if (onlyDeleteConfig)
+ api.deleteConfigurationValues(instanceId, configName, true)
+ else
+ api.editConfigurationValuesByDelete(instanceId, configName)
+ } else {
+ if (onlyDeleteConfig)
+ log.info("Configuration does not exists - skipping delete")
+ else
+ throw BluePrintProcessorException(
+ "Configuration $configName does not exist. Cannot perform delete operation"
+ )
+ }
}
}
@@ -261,6 +289,6 @@ open class K8sConfigValueComponent(
}
private enum class OperationType {
- CREATE, UPDATE, DELETE
+ CREATE, UPDATE, DELETE, ROLLBACK, DELETE_CONFIG
}
}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sTemplate.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sTemplate.kt
index e135d21ce..26c834650 100644
--- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sTemplate.kt
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sTemplate.kt
@@ -12,8 +12,14 @@ class K8sTemplate {
@get:JsonProperty("description")
var description: String? = null
+ @get:JsonProperty("chart-name")
+ var chartName: String? = null
+
+ @get:JsonProperty("has-content")
+ var hasContent: Boolean? = null
+
override fun toString(): String {
- return "$templateName:$description"
+ return "$templateName:$description:$chartName:$hasContent"
}
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/K8sConfigValueResponse.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt
index cefe3e605..c1eaed8bd 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
@@ -29,7 +29,10 @@ class K8sConfigValueResponse {
@get:JsonProperty("config-version")
@get:JsonAlias("config-verion")
- var configVersion: Integer? = null
+ var configVersion: Int? = null
+
+ @get:JsonProperty("config-tag")
+ var configTag: String? = null
@get:JsonProperty("values")
var values: Map<String, Object>? = null
@@ -48,3 +51,26 @@ class K8sConfigValueResponse {
return javaClass.hashCode()
}
}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sConfigValueTag {
+ @get:JsonProperty("config-version")
+ var configVersion: Int? = null
+
+ @get:JsonProperty("config-tag")
+ var configTag: String? = null
+
+ override fun toString(): String {
+ return "$configVersion:$configTag"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
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 dbcc13395..1be45e892 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
@@ -1,7 +1,7 @@
/*
* Copyright © 2017-2018 AT&T Intellectual Property.
* Modifications Copyright © 2019 IBM.
- * Modifications Copyright © 2021 Orange.
+ * Modifications Copyright © 2022 Orange.
* Modifications Copyright © 2020 Deutsche Telekom AG.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -84,6 +84,28 @@ class K8sPluginInstanceApi(
}
}
+ fun getFullInstanceById(instanceId: String): K8sRbInstanceFull? {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ GET.name,
+ "?full=true",
+ ""
+ )
+ log.debug(result.toString())
+ return if (result.status in 200..299) {
+ val parsedObject: K8sRbInstanceFull? = JacksonUtils.readValue(result.body, K8sRbInstanceFull::class.java)
+ parsedObject
+ } else if (result.status == 500 && result.body.contains("Error finding master table"))
+ null
+ else
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s rb instance")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
fun getInstanceByRequestProperties(
rbDefinitionName: String,
rbDefinitionVersion: String,
@@ -281,6 +303,28 @@ class K8sPluginInstanceApi(
}
}
+ fun editConfigurationValuesByDelete(instanceId: String, configName: String): K8sConfigValueResponse? {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ POST.name,
+ "/config/$configName/delete",
+ ""
+ )
+ 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 delete config instance")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
fun hasConfigurationValues(instanceId: String, configName: String): Boolean {
val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
try {
@@ -297,6 +341,22 @@ class K8sPluginInstanceApi(
}
}
+ fun hasConfigurationValuesVersion(instanceId: String, configName: String, version: String): Boolean {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ GET.name,
+ "/config/$configName/version/$version",
+ ""
+ )
+ log.debug(result.toString())
+ return result.status in 200..299
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s rb instance")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
fun getConfigurationValues(instanceId: String, configName: String): K8sConfigValueResponse? {
val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
try {
@@ -319,29 +379,34 @@ class K8sPluginInstanceApi(
}
}
- fun deleteConfigurationValues(instanceId: String, configName: String) {
+ fun getConfigurationValuesVersion(instanceId: String, configName: String, version: String): K8sConfigValueResponse? {
val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
try {
val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
- DELETE.name,
- "/config/$configName",
+ GET.name,
+ "/config/$configName/version/$version",
""
)
log.debug(result.toString())
- if (result.status !in 200..299)
+ 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 delete config instance")
+ log.error("Caught exception trying to get config instance")
throw BluePrintProcessorException("${e.message}")
}
}
- fun rollbackConfigurationValues(instanceId: String): K8sConfigValueResponse? {
+ fun getConfigurationValuesVersionByTag(instanceId: String, configName: String, tag: String): K8sConfigValueResponse? {
val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
try {
val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
- POST.name,
- "/rollback",
+ GET.name,
+ "/config/$configName/tag/$tag",
""
)
log.debug(result.toString())
@@ -353,29 +418,137 @@ 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 get config instance")
throw BluePrintProcessorException("${e.message}")
}
}
- fun tagConfigurationValues(instanceId: String): K8sConfigValueResponse? {
+ fun getConfigurationValuesList(instanceId: String): List<K8sConfigValueResponse>? {
val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
try {
val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
- POST.name,
- "/tagit",
+ GET.name,
+ "/config",
""
)
log.debug(result.toString())
return if (result.status in 200..299) {
- val parsedObject: K8sConfigValueResponse? = JacksonUtils.readValue(
- result.body, K8sConfigValueResponse::class.java
- )
+ val objectMapper = jacksonObjectMapper()
+ val parsedObject: ArrayList<K8sConfigValueResponse>? = objectMapper.readValue(result.body)
parsedObject
- } else
+ } else if (result.status == 500 && result.body.contains("Did not find any objects with tag"))
+ null
+ else
throw BluePrintProcessorException(result.body)
} catch (e: Exception) {
- log.error("Caught exception trying to get k8s rb instance")
+ log.error("Caught exception trying to get k8s config instance list")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
+ fun getConfigurationValuesVersionList(instanceId: String, configName: String): List<K8sConfigValueResponse>? {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ GET.name,
+ "/config/$configName/version",
+ ""
+ )
+ log.debug(result.toString())
+ return if (result.status in 200..299) {
+ val objectMapper = jacksonObjectMapper()
+ val parsedObject: ArrayList<K8sConfigValueResponse>? = objectMapper.readValue(result.body)
+ parsedObject
+ } else if (result.status == 500 && result.body.contains("Did not find any objects with tag"))
+ null
+ else
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s config instance version list")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
+ fun getConfigurationValuesTagList(instanceId: String, configName: String): List<K8sConfigValueTag>? {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ GET.name,
+ "/config/$configName/tag",
+ ""
+ )
+ log.debug(result.toString())
+ return if (result.status in 200..299) {
+ val objectMapper = jacksonObjectMapper()
+ val parsedObject: ArrayList<K8sConfigValueTag>? = objectMapper.readValue(result.body)
+ parsedObject
+ } else if (result.status == 500 && result.body.contains("Did not find any objects with tag"))
+ null
+ else
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s config instance tag list")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
+ fun deleteConfigurationValues(instanceId: String, configName: String, deleteConfigOnly: Boolean) {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ var path: String = "/config/$configName"
+ if (deleteConfigOnly)
+ path = path.plus("?deleteConfigOnly=true")
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ DELETE.name,
+ path,
+ ""
+ )
+ 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, configName: String, configVersion: String?, configTag: String?) {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val configValues = hashMapOf<String, String>()
+ if (configVersion != null)
+ configValues["config-version"] = configVersion
+ if (configTag != null)
+ configValues["config-tag"] = configTag
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ POST.name,
+ "/config/$configName/rollback",
+ JacksonUtils.getJson(configValues)
+ )
+ log.debug(result.toString())
+ if (result.status !in 200..299)
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to rollback config instance")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+
+ fun tagConfigurationValues(instanceId: String, configName: String, tagName: String) {
+ val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId)
+ try {
+ val configValues = hashMapOf<String, String>()
+ configValues["tag-name"] = tagName
+ val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource(
+ POST.name,
+ "/config/$configName/tagit",
+ JacksonUtils.getJson(configValues)
+ )
+ log.debug(result.toString())
+ if (result.status !in 200..299)
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to tag config instance")
throw BluePrintProcessorException("${e.message}")
}
}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sRbInstance.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sRbInstance.kt
index 2b40d3517..97175e26f 100644
--- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sRbInstance.kt
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sRbInstance.kt
@@ -1,7 +1,7 @@
/*
* Copyright © 2017-2018 AT&T Intellectual Property.
* Modifications Copyright © 2019 IBM.
- * Modifications Copyright © 2021 Orange.
+ * Modifications Copyright © 2022 Orange.
* Modifications Copyright © 2020 Deutsche Telekom AG.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,6 +55,49 @@ class K8sRbInstance {
}
}
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sRbInstanceFull {
+
+ @get:JsonProperty("id")
+ var id: String? = null
+
+ @get:JsonProperty("namespace")
+ var namespace: String? = "default"
+
+ @get:JsonProperty("status")
+ var status: String? = null
+
+ @get:JsonProperty("hook-progress")
+ var hookProgress: String? = null
+
+ @get:JsonProperty("request")
+ var request: K8sRbInstanceRequest? = null
+
+ @get:JsonProperty("release-name")
+ var releaseName: String? = null
+
+ @get:JsonProperty("resources")
+ var resources: List<K8sRbInstanceResource>? = null
+
+ @get:JsonProperty("hooks")
+ var hooks: List<K8sRbInstanceHookDefinition>? = null
+
+ override fun toString(): String {
+ return "$id:$releaseName:$namespace"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
class K8sRbInstanceResource {
@get:JsonProperty("Name")
@@ -77,3 +120,112 @@ class K8sRbInstanceResource {
return javaClass.hashCode()
}
}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sRbInstanceHookDefinition {
+
+ @get:JsonProperty("Hook")
+ var hook: K8sRbInstanceHook? = null
+
+ @get:JsonProperty("KRT")
+ var krt: K8sRbInstanceKrt? = null
+
+ override fun toString(): String {
+ return "$hook"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sRbInstanceHook {
+
+ @get:JsonProperty("name")
+ var name: String? = null
+
+ @get:JsonProperty("kind")
+ var kind: String? = null
+
+ @get:JsonProperty("path")
+ var path: String? = null
+
+ @get:JsonProperty("manifest")
+ var manifest: String? = null
+
+ @get:JsonProperty("weight")
+ var weight: Int? = 0
+
+ @get:JsonProperty("last_run")
+ var lastRun: K8sRbInstanceHookExecution? = null
+
+ @get:JsonProperty("events")
+ var events: List<String>? = null
+
+ @get:JsonProperty("delete_policies")
+ var deletePolicies: List<String>? = null
+
+ override fun toString(): String {
+ return "$name"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sRbInstanceHookExecution {
+
+ @get:JsonProperty("started_at")
+ var startedAt: String? = null
+
+ @get:JsonProperty("completed_at")
+ var completedAt: String? = null
+
+ @get:JsonProperty("phase")
+ var phase: String? = null
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sRbInstanceKrt {
+
+ @get:JsonProperty("FilePath")
+ var filePath: String? = null
+
+ @get:JsonProperty("GVK")
+ var gvk: K8sRbInstanceGvk? = null
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sPluginQueryApi.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sPluginQueryApi.kt
new file mode 100644
index 000000000..170f29566
--- /dev/null
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sPluginQueryApi.kt
@@ -0,0 +1,56 @@
+package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.query;
+
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
+import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
+import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpMethod.GET
+
+public class K8sPluginQueryApi(
+ private val k8sConfiguration: K8sConnectionPluginConfiguration
+) {
+ private val log = LoggerFactory.getLogger(K8sPluginQueryApi::class.java)!!
+
+ fun queryK8sResources(
+ cloudRegion: String,
+ kind: String,
+ apiVersion: String,
+ name: String? = null,
+ namespace: String? = null,
+ labels: Map<String, String>? = null
+ ): K8sResourceStatus? {
+ val rbQueryService = K8sQueryRestClient(k8sConfiguration)
+ try {
+ var path: String = "?CloudRegion=$cloudRegion&ApiVersion=$apiVersion&Kind=$kind"
+ if (name != null)
+ path = path.plus("&Name=$name")
+ if (namespace != null)
+ path = path.plus("&Namespace=$name")
+ 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> = rbQueryService.exchangeResource(
+ GET.name,
+ path,
+ ""
+ )
+ log.debug(result.toString())
+ return if (result.status in 200..299) {
+ val parsedObject: K8sResourceStatus? = JacksonUtils.readValue(
+ result.body, K8sResourceStatus::class.java
+ )
+ parsedObject
+ } else if (result.status == 500 && result.body.contains("Error finding master table"))
+ null
+ else
+ throw BluePrintProcessorException(result.body)
+ } catch (e: Exception) {
+ log.error("Caught exception trying to get k8s rb instance")
+ throw BluePrintProcessorException("${e.message}")
+ }
+ }
+}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sQueryRestClient.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sQueryRestClient.kt
new file mode 100644
index 000000000..dda5a6eea
--- /dev/null
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sQueryRestClient.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2017-2018 AT&T Intellectual Property.
+ * Modifications Copyright © 2019 IBM.
+ * Modifications Copyright © 2022 Orange.
+ *
+ * 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.k8s.query
+
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sAbstractRestClientService
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
+
+open class K8sQueryRestClient(
+ k8sConfiguration: K8sConnectionPluginConfiguration
+) : K8sAbstractRestClientService(k8sConfiguration) {
+
+ override fun apiUrl(): String {
+ return "$baseUrl/v1/query"
+ }
+}
diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sResourceStatus.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sResourceStatus.kt
new file mode 100644
index 000000000..d0e84ae8c
--- /dev/null
+++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/query/K8sResourceStatus.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2017-2018 AT&T Intellectual Property.
+ * Modifications Copyright © 2019 IBM.
+ * Modifications Copyright © 2022 Orange.
+ *
+ * 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.k8s.query
+
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sRbInstanceResourceStatus
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonProperty
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class K8sResourceStatus {
+
+ @get:JsonProperty("resourceCount")
+ var resourceCount: Int = 0
+
+ @get:JsonProperty("resourcesStatus")
+ var resourcesStatus: List<K8sRbInstanceResourceStatus>? = null
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+} \ No newline at end of file