diff options
author | KAPIL SINGAL <ks220y@att.com> | 2021-02-20 03:34:07 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2021-02-20 03:34:07 +0000 |
commit | 8e190907c60472e3ad34c8f2185411725e358926 (patch) | |
tree | 2ed5c23abec0f89e69678f2103c9bb0fa2b78b84 | |
parent | 06e30271064052de497480d17a046b49a58c5711 (diff) | |
parent | 64269c0d34d701a2a85dcfa901de200d4886814f (diff) |
Merge "Wrap day2 api and create configuration-value component"
6 files changed, 397 insertions, 17 deletions
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 4e20dcb03..cb759e534 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 @@ -75,7 +75,7 @@ open class K8sConfigTemplateComponent( private val log = LoggerFactory.getLogger(K8sConfigTemplateComponent::class.java)!! override suspend fun processNB(executionRequest: ExecutionServiceInput) { - log.info("Triggering K8s Profile Upload component logic.") + log.info("Triggering K8s Config Upload component logic.") val inputParameterNames = arrayOf( INPUT_K8S_TEMPLATE_NAME, @@ -153,7 +153,7 @@ open class K8sConfigTemplateComponent( api.createTemplate(definitionName, definitionVersion, template) api.uploadTemplate(definitionName, definitionVersion, template, templateFilePath) - log.info("K8s Profile Upload Completed") + log.info("K8s Config Upload Completed") outputPrefixStatuses[prefix] = OUTPUT_UPLOADED } } @@ -183,17 +183,17 @@ open class K8sConfigTemplateComponent( return result } - private suspend fun prepareTemplateFile(k8sRbTemplateName: String, ks8ProfileSource: String, ks8ProfileLocation: String): Path { + private suspend fun prepareTemplateFile(k8sRbTemplateName: String, ks8ConfigSource: String, k8sConfigLocation: String): Path { val bluePrintContext = bluePrintRuntimeService.bluePrintContext() val bluePrintBasePath: String = bluePrintContext.rootPath - val profileSourceFileFolderPath: Path = Paths.get( - bluePrintBasePath.plus(File.separator).plus(ks8ProfileLocation) + val configeSourceFileFolderPath: Path = Paths.get( + bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation) ) - if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory) - return profileSourceFileFolderPath - else if (profileSourceFileFolderPath.toFile().exists()) { - log.info("Profile building started from source $ks8ProfileSource") + if (configeSourceFileFolderPath.toFile().exists() && !configeSourceFileFolderPath.toFile().isDirectory) + return configeSourceFileFolderPath + else if (configeSourceFileFolderPath.toFile().exists()) { + log.info("Config building started from source $ks8ConfigSource") val properties: MutableMap<String, Any> = mutableMapOf() properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = "" @@ -204,28 +204,28 @@ open class K8sConfigTemplateComponent( val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources( bluePrintRuntimeService, nodeTemplateName, - ks8ProfileSource, + ks8ConfigSource, properties ) val tempMainPath: File = createTempDir("k8s-profile-", "") - val tempProfilePath: File = createTempDir("content-", "", tempMainPath) + val tempPath: File = createTempDir("content-", "", tempMainPath) val resolvedJsonContent = resolutionResult.second .associateBy({ it.name }, { it.property?.value }) .asJsonNode() try { - templateLocation(profileSourceFileFolderPath.toFile(), resolvedJsonContent, tempProfilePath) - // Preparation of the final profile content + templateLocation(configeSourceFileFolderPath.toFile(), resolvedJsonContent, tempPath) + // Preparation of the final config content val finalTemplateFilePath = Paths.get( tempMainPath.toString().plus(File.separator).plus( "$k8sRbTemplateName.tar.gz" ) ) - if (!BlueprintArchiveUtils.compress(tempProfilePath, finalTemplateFilePath.toFile(), ArchiveType.TarGz)) { - throw BlueprintProcessorException("Profile compression has failed") + if (!BlueprintArchiveUtils.compress(tempPath, finalTemplateFilePath.toFile(), ArchiveType.TarGz)) { + throw BlueprintProcessorException("Config template compression has failed") } - FileUtils.deleteDirectory(tempProfilePath) + FileUtils.deleteDirectory(tempPath) return finalTemplateFilePath } catch (t: Throwable) { @@ -233,7 +233,7 @@ open class K8sConfigTemplateComponent( throw t } } else - throw BlueprintProcessorException("Profile source $ks8ProfileLocation is missing in CBA folder") + throw BlueprintProcessorException("Config source $k8sConfigLocation is missing in CBA folder") } private fun templateLocation(location: File, params: JsonNode, destinationFolder: File) { 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 new file mode 100644 index 000000000..7e9f407e1 --- /dev/null +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/definition/template/K8sConfigValueComponent.kt @@ -0,0 +1,149 @@ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.ArrayNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.fasterxml.jackson.module.kotlin.convertValue +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.instance.K8sConfigValueRequest +import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sPluginInstanceApi +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.data.ArtifactDefinition +import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Component +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +@Component("component-k8s-config-value") +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +open class K8sConfigValueComponent( + private var bluePrintPropertiesService: BlueprintPropertiesService +) : AbstractComponentFunction() { + + private val log = LoggerFactory.getLogger(K8sConfigValueComponent::class.java)!! + + 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_INSTANCE_ID = "k8s-instance-id" + const val INPUT_K8S_TEMPLATE_VALUE_SOURCE = "k8s-rb-template-value-source" + + const val OUTPUT_STATUSES = "statuses" + const val OUTPUT_SKIPPED = "skipped" + const val OUTPUT_UPLOADED = "uploaded" + const val OUTPUT_ERROR = "error" + } + + 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_INSTANCE_ID, + INPUT_K8S_TEMPLATE_VALUE_SOURCE, + INPUT_ARTIFACT_PREFIX_NAMES + ) + val outputPrefixStatuses = mutableMapOf<String, String>() + val inputParamsMap = mutableMapOf<String, JsonNode?>() + + inputParameterNames.forEach { + inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing() + } + + log.info("Getting the template prefixes") + val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES]) + + log.info("Iterating over prefixes in resource assignment map.") + for (prefix in prefixList) { + outputPrefixStatuses[prefix] = OUTPUT_SKIPPED + val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue + val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode + val prefixInputParamsMap = inputParamsMap.toMutableMap() + prefixInputParamsMap.forEach { (inputParamName, value) -> + if (value == null) { + val mapValue = assignmentMapPrefix.get(inputParamName) + log.debug("$inputParamName value was $value so we fetch $mapValue") + prefixInputParamsMap[inputParamName] = mapValue + } + } + + val templateName: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_NAME]?.returnNullIfMissing()?.asText() + val configName: String? = prefixInputParamsMap[INPUT_K8S_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() + if (templateName == null || instanceId == null || valueSource == null) { + log.warn("$INPUT_K8S_TEMPLATE_NAME or $INPUT_K8S_TEMPLATE_NAME or $INPUT_K8S_TEMPLATE_VALUE_SOURCE or $INPUT_K8S_CONFIG_NAME is null") + } else if (templateName.isEmpty()) { + log.warn("$INPUT_K8S_TEMPLATE_NAME is empty") + } else { + log.info("Uploading K8s template value..") + outputPrefixStatuses[prefix] = OUTPUT_ERROR + val bluePrintContext = bluePrintRuntimeService.bluePrintContext() + val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource) + if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE) + throw BlueprintProcessorException( + "Unexpected profile artifact type for profile 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) + api.createConfigurationValues(configValueRequest, instanceId) + } + } + } + + private fun parseResult(templateValueSource: String): Any { + val ymlSourceFile = getYmlSourceFile(templateValueSource) + val yamlReader = ObjectMapper(YAMLFactory()) + val obj: Any = yamlReader.readValue(ymlSourceFile, Any::class.java) + + val jsonWriter = ObjectMapper() + return jsonWriter.convertValue(obj) + } + + private fun getYmlSourceFile(templateValueSource: String): File { + val bluePrintBasePath: String = bluePrintRuntimeService.bluePrintContext().rootPath + val profileSourceFileFolderPath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus(templateValueSource)) + + if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory) + return profileSourceFileFolderPath.toFile() + else + throw BlueprintProcessorException("Template value $profileSourceFileFolderPath is missing in CBA folder") + } + + private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> { + val result = ArrayList<String>() + when (node) { + is ArrayNode -> { + val arrayNode = node.toList() + for (prefixNode in arrayNode) + result.add(prefixNode.asText()) + } + is ObjectNode -> { + result.add(node.asText()) + } + } + return result + } + + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + bluePrintRuntimeService.getBlueprintError().addError(runtimeException.message!!) + } +} diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueRequest.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueRequest.kt new file mode 100644 index 000000000..b6093d68c --- /dev/null +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueRequest.kt @@ -0,0 +1,32 @@ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance + +import com.fasterxml.jackson.annotation.JsonProperty + +class K8sConfigValueRequest { + + @get:JsonProperty("template-name") + var templateName: String? = null + + @get:JsonProperty("config-name") + var configName: String? = null + + @get:JsonProperty("description") + var description: String? = null + + @get:JsonProperty("values") + var values: Any? = null + + override fun toString(): String { + return "$templateName:$configName:$description:$values" + } + + 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/K8sConfigValueResponse.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt new file mode 100644 index 000000000..823009fbb --- /dev/null +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sConfigValueResponse.kt @@ -0,0 +1,38 @@ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance + +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("profile-name") + var profileName: 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 + + override fun toString(): String { + return "$rbName:$rbVersion:$profileName:$templateName:$configName:$configVersion" + } + + 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 e0366f994..b312adc8c 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 @@ -30,6 +30,7 @@ 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 class K8sPluginInstanceApi( private val k8sConfiguration: K8sConnectionPluginConfiguration @@ -222,6 +223,134 @@ class K8sPluginInstanceApi( } } + fun createConfigurationValues(configValues: K8sConfigValueRequest, instanceId: String): K8sConfigValueResponse? { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + POST.name, + "/config", + JacksonUtils.getJson(configValues) + ) + 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 k8s rb instance") + throw BlueprintProcessorException("${e.message}") + } + } + + fun editConfigurationValues(configValues: K8sConfigValueRequest, instanceId: String, configName: String): K8sConfigValueResponse? { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + PUT.name, + "/config/$configName", + JacksonUtils.getJson(configValues) + ) + 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 k8s rb instance") + throw BlueprintProcessorException("${e.message}") + } + } + + 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 k8s rb 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 get k8s rb instance") + throw BlueprintProcessorException("${e.message}") + } + } + + fun rollbackConfigurationValues(instanceId: String): K8sConfigValueResponse? { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + POST.name, + "/rollback", + "" + ) + 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 k8s rb instance") + throw BlueprintProcessorException("${e.message}") + } + } + + fun createConfigurationValues(instanceId: String): K8sConfigValueResponse? { + val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) + try { + val result: BlueprintWebClientService.WebClientResponse<String> = rbInstanceService.exchangeResource( + POST.name, + "/tagit", + "" + ) + 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 k8s rb instance") + throw BlueprintProcessorException("${e.message}") + } + } + fun deleteInstanceHealthCheck(instanceId: String, healthCheckId: String) { val rbInstanceService = K8sRbInstanceRestClient(k8sConfiguration, instanceId) try { diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sTopic.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sTopic.kt new file mode 100644 index 000000000..7ff18ade6 --- /dev/null +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/instance/K8sTopic.kt @@ -0,0 +1,32 @@ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance + +import com.fasterxml.jackson.annotation.JsonProperty + +class K8sTopic { + + @get:JsonProperty("name") + var name: String? = null + + @get:JsonProperty("cluster") + var cluster: String? = null + + @get:JsonProperty("partitions") + var partitions: Number? = null + + @get:JsonProperty("replicas") + var replicas: Number? = null + + override fun toString(): String { + return "$name:$cluster:$partitions:$replicas" + } + + 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() + } +} |