diff --git a/ydb/core/viewer/tests/canondata/result.json b/ydb/core/viewer/tests/canondata/result.json index 89bf8119ad2b..3e6200f3d290 100644 --- a/ydb/core/viewer/tests/canondata/result.json +++ b/ydb/core/viewer/tests/canondata/result.json @@ -2114,7 +2114,7 @@ "PathId": "not-zero-number-text", "PathState": 2, "PathType": 1, - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" }, { "ACL": "", @@ -2127,7 +2127,7 @@ "PathId": "not-zero-number-text", "PathState": 2, "PathType": 10, - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" }, { "ACL": "", @@ -2140,7 +2140,7 @@ "PathId": "not-zero-number-text", "PathState": 3, "PathType": 10, - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" }, { "ACL": "", @@ -2153,7 +2153,7 @@ "PathId": "not-zero-number-text", "PathState": 2, "PathType": 10, - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" }, { "CreateFinished": true, @@ -2161,14 +2161,14 @@ "CreateTxId": "0", "Name": ".sys", "PathType": 1, - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" } ], "DomainDescription": { "DiskSpaceUsage": "not-empty-object", "DomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "PQPartitionsInside": "0", "PQPartitionsLimit": "1000000", @@ -2187,9 +2187,9 @@ }, "ResourcesDomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, - "SchemeShardId_Depricated": "72075186232723360", + "SchemeShardId_Depricated": "72057594046678944", "SecurityState": { "Audience": "/Root", "Sids": [ @@ -2287,11 +2287,11 @@ "PathSubType": 0, "PathType": 1, "PathVersion": "12", - "SchemeshardId": "72075186232723360" + "SchemeshardId": "72057594046678944" } }, "PathId": "not-zero-number-text", - "PathOwnerId": "72075186232723360", + "PathOwnerId": "72057594046678944", "Source": 1, "Status": "StatusSuccess" }, @@ -2327,7 +2327,7 @@ "DiskSpaceUsage": "not-empty-object", "DomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "PQPartitionsInside": "0", "PQPartitionsLimit": "1000000", @@ -2354,9 +2354,9 @@ }, "ResourcesDomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, - "SchemeShardId_Depricated": "72075186232723360", + "SchemeShardId_Depricated": "72057594046678944", "SecurityState": { "Audience": "/Root/dedicated_db" }, @@ -2409,7 +2409,7 @@ "DiskSpaceUsage": "not-empty-object", "DomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "PQPartitionsInside": "0", "PQPartitionsLimit": "1000000", @@ -2432,9 +2432,9 @@ }, "ResourcesDomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, - "SchemeShardId_Depricated": "72075186232723360", + "SchemeShardId_Depricated": "72057594046678944", "SecurityState": { "Audience": "/Root/serverless_db" }, @@ -2503,7 +2503,7 @@ "DiskSpaceUsage": "not-empty-object", "DomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "PQPartitionsInside": "0", "PQPartitionsLimit": "1000000", @@ -2530,9 +2530,9 @@ }, "ResourcesDomainKey": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, - "SchemeShardId_Depricated": "72075186232723360", + "SchemeShardId_Depricated": "72057594046678944", "SecurityState": { "Audience": "/Root/shared_db" }, @@ -2656,7 +2656,7 @@ "PeerNodeId": 50000, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "6" }, "Utilization": "number" @@ -2673,7 +2673,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -3021,7 +3021,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -3045,7 +3045,7 @@ "PeerNodeId": 50000, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "6" }, "Utilization": "number" @@ -3062,7 +3062,7 @@ "PeerNodeId": 50000, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "6" }, "Utilization": "number" @@ -3222,7 +3222,7 @@ "PeerNodeId": 50000, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "6" }, "Utilization": "number" @@ -3246,7 +3246,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -3263,7 +3263,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -3413,7 +3413,7 @@ "PeerNodeId": 50000, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "6" }, "Utilization": "number" @@ -3437,7 +3437,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -3454,7 +3454,7 @@ "PeerNodeId": 50001, "PingTimeUs": "text", "ScopeId": { - "X1": "72075186232723360", + "X1": "72057594046678944", "X2": "7" }, "Utilization": "number" @@ -4380,7 +4380,7 @@ "NodeId": 1, "Overall": 1, "State": 10, - "TabletId": "72075186232723360", + "TabletId": "72057594046678944", "Type": 16 } ] @@ -4402,7 +4402,7 @@ "TabletId": "72075186224037888", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 14 }, @@ -4418,7 +4418,7 @@ "TabletId": "72075186224037889", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4434,7 +4434,7 @@ "TabletId": "72075186224037890", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4450,7 +4450,7 @@ "TabletId": "72075186224037891", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 32 }, @@ -4466,7 +4466,7 @@ "TabletId": "72075186224037892", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4482,7 +4482,7 @@ "TabletId": "72075186224037893", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4498,7 +4498,7 @@ "TabletId": "72075186224037894", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 40 }, @@ -4514,7 +4514,7 @@ "TabletId": "72075186224037895", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4530,7 +4530,7 @@ "TabletId": "72075186224037896", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4546,7 +4546,7 @@ "TabletId": "72075186224037897", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 16 } @@ -4569,7 +4569,7 @@ "TabletId": "72075186224038899", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 16 }, @@ -4585,7 +4585,7 @@ "TabletId": "72075186224038900", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4601,7 +4601,7 @@ "TabletId": "72075186224038901", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4617,7 +4617,7 @@ "TabletId": "72075186224038902", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4633,7 +4633,7 @@ "TabletId": "72075186224038903", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4649,7 +4649,7 @@ "TabletId": "72075186224038904", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 32 } @@ -4672,7 +4672,7 @@ "TabletId": "72075186224038889", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 14 }, @@ -4688,7 +4688,7 @@ "TabletId": "72075186224038890", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 40 }, @@ -4704,7 +4704,7 @@ "TabletId": "72075186224038891", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4720,7 +4720,7 @@ "TabletId": "72075186224038892", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4736,7 +4736,7 @@ "TabletId": "72075186224038893", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 32 }, @@ -4752,7 +4752,7 @@ "TabletId": "72075186224038894", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4768,7 +4768,7 @@ "TabletId": "72075186224038895", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4784,7 +4784,7 @@ "TabletId": "72075186224038896", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 13 }, @@ -4800,7 +4800,7 @@ "TabletId": "72075186224038897", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 5 }, @@ -4816,7 +4816,7 @@ "TabletId": "72075186224038898", "TenantId": { "PathId": "not-zero-number-text", - "SchemeShard": "72075186232723360" + "SchemeShard": "72057594046678944" }, "Type": 16 } @@ -4957,7 +4957,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "CreateTime": "not-zero-number-text", - "Id": "72075186232723360-1", + "Id": "72057594046678944-1", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": {}, @@ -5004,7 +5004,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-6", + "Id": "72057594046678944-6", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": "not-empty-object", @@ -5071,12 +5071,12 @@ { "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-8", + "Id": "72057594046678944-8", "Metrics": "not-empty-object", "Name": "/Root/serverless_db", "Overall": "Green", "Owner": "root@builtin", - "ResourceId": "72075186232723360-7", + "ResourceId": "72057594046678944-7", "State": "RUNNING", "StateStats": [ { @@ -5091,7 +5091,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-7", + "Id": "72057594046678944-7", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": "not-empty-object", @@ -5158,7 +5158,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "CreateTime": "not-zero-number-text", - "Id": "72075186232723360-1", + "Id": "72057594046678944-1", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": {}, @@ -5209,7 +5209,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-6", + "Id": "72057594046678944-6", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": "not-empty-object", @@ -5280,11 +5280,11 @@ { "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-8", + "Id": "72057594046678944-8", "Metrics": "not-empty-object", "Name": "/Root/serverless_db", "Owner": "root@builtin", - "ResourceId": "72075186232723360-7", + "ResourceId": "72057594046678944-7", "State": "RUNNING", "StateStats": [ { @@ -5303,7 +5303,7 @@ "CoresTotal": "not-zero-number", "CoresUsed": "not-zero-number", "DatabaseQuotas": {}, - "Id": "72075186232723360-7", + "Id": "72057594046678944-7", "MemoryLimit": "not-zero-number-text", "MemoryStats": {}, "Metrics": "not-empty-object", diff --git a/ydb/tests/functional/config/test_config_migration.py b/ydb/tests/functional/config/test_config_migration.py new file mode 100644 index 000000000000..fef049e88fd1 --- /dev/null +++ b/ydb/tests/functional/config/test_config_migration.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +import logging +import yaml +import time +from hamcrest import assert_that + +from ydb.tests.library.common.types import Erasure +import ydb.tests.library.common.cms as cms +from ydb.tests.library.clients.kikimr_http_client import SwaggerClient +from ydb.tests.library.harness.kikimr_runner import KiKiMR +from ydb.tests.library.clients.kikimr_config_client import ConfigClient +from ydb.tests.library.clients.kikimr_dynconfig_client import DynConfigClient +from ydb.tests.library.harness.kikimr_config import KikimrConfigGenerator +from ydb.tests.library.kv.helpers import create_kv_tablets_and_wait_for_start +from ydb.public.api.protos.ydb_status_codes_pb2 import StatusIds +from ydb.tests.library.harness.util import LogLevels + +import ydb.public.api.protos.ydb_config_pb2 as config +import ydb.public.api.protos.draft.ydb_dynamic_config_pb2 as dynconfig + +logger = logging.getLogger(__name__) + + +def generate_config(dynconfig_client): + generate_config_response = dynconfig_client.fetch_startup_config() + assert_that(generate_config_response.operation.status == StatusIds.SUCCESS) + + result = dynconfig.FetchStartupConfigResult() + generate_config_response.operation.result.Unpack(result) + return result.config + + +def fetch_config_dynconfig(dynconfig_client): + fetch_config_response = dynconfig_client.fetch_config() + assert_that(fetch_config_response.operation.status == StatusIds.SUCCESS) + + result = dynconfig.GetConfigResult() + fetch_config_response.operation.result.Unpack(result) + if result.config[0] == "": + return None + else: + return result.config[0] + + +def replace_config(config_client, config): + replace_config_response = config_client.replace_config(config) + assert_that(replace_config_response.operation.status == StatusIds.SUCCESS) + + +def replace_config_dynconfig(dynconfig_client, config): + replace_config_response = dynconfig_client.replace_config(config) + assert_that(replace_config_response.operation.status == StatusIds.SUCCESS) + + +def value_for(key, tablet_id): + return "Value: ".format( + key=key, tablet_id=tablet_id) + + +def fetch_config(config_client): + fetch_config_response = config_client.fetch_all_configs() + assert_that(fetch_config_response.operation.status == StatusIds.SUCCESS) + + result = config.FetchConfigResult() + fetch_config_response.operation.result.Unpack(result) + if result.config: + return result.config[0].config + else: + return None + + +class TestConfigMigrationToV2(object): + erasure = Erasure.BLOCK_4_2 + separate_node_configs = True + metadata_section = { + "kind": "MainConfig", + "version": 0, + "cluster": "", + } + + @classmethod + def setup_class(cls): + nodes_count = 8 if cls.erasure == Erasure.BLOCK_4_2 else 9 + log_configs = { + 'BS_NODE': LogLevels.DEBUG, + 'GRPC_SERVER': LogLevels.DEBUG, + 'GRPC_PROXY': LogLevels.DEBUG, + 'TX_PROXY': LogLevels.DEBUG, + 'TICKET_PARSER': LogLevels.DEBUG, + 'BS_CONTROLLER': LogLevels.DEBUG, + 'TABLET_EXECUTOR': LogLevels.DEBUG, + 'TABLET_MAIN': LogLevels.DEBUG, + } + cls.configurator = KikimrConfigGenerator( + cls.erasure, + nodes=nodes_count, + use_in_memory_pdisks=False, + separate_node_configs=cls.separate_node_configs, + simple_config=True, + extra_grpc_services=['config'], + additional_log_configs=log_configs, + explicit_hosts_and_host_configs=True, + ) + + cls.cluster = KiKiMR(configurator=cls.configurator) + cls.cluster.start() + + cms.request_increase_ratio_limit(cls.cluster.client) + host = cls.cluster.nodes[1].host + grpc_port = cls.cluster.nodes[1].port + cls.swagger_client = SwaggerClient(host, cls.cluster.nodes[1].mon_port) + cls.config_client = ConfigClient(host, grpc_port) + cls.dynconfig_client = DynConfigClient(host, grpc_port) + + @classmethod + def teardown_class(cls): + cls.cluster.stop() + + def check_kikimr_is_operational(self, table_path, tablet_ids): + for partition_id, tablet_id in enumerate(tablet_ids): + write_resp = self.cluster.kv_client.kv_write( + table_path, partition_id, "key", value_for("key", tablet_id) + ) + assert_that(write_resp.operation.status == StatusIds.SUCCESS) + + read_resp = self.cluster.kv_client.kv_read( + table_path, partition_id, "key" + ) + assert_that(read_resp.operation.status == StatusIds.SUCCESS) + + def wait_for_all_nodes_start(self, expected_nodes_count, timeout_seconds=120): + start_time = time.time() + logger.info(f"Waiting for {expected_nodes_count} nodes to start and report Green status...") + last_exception = None + up_nodes_count = 0 + reported_nodes = 0 + + while time.time() - start_time < timeout_seconds: + try: + nodes_info = self.swagger_client.nodes_info() + if nodes_info and 'Nodes' in nodes_info: + current_up_nodes = 0 + reported_nodes = len(nodes_info['Nodes']) + for node_status in nodes_info['Nodes']: + system_state = node_status.get('SystemState', {}) + if system_state.get('SystemState') == 'Green': + current_up_nodes += 1 + up_nodes_count = current_up_nodes + + logger.debug(f"Node status check: {up_nodes_count}/{expected_nodes_count} Green, {reported_nodes} reported.") + if up_nodes_count == expected_nodes_count: + logger.info(f"All {expected_nodes_count} nodes reported Green status.") + return True + else: + logger.debug("Waiting for nodes: Node info not available or empty in response.") + + except Exception as e: + logger.debug(f"Error fetching node status, retrying: {e}") + last_exception = e + + time.sleep(2) + + error_message = ( + f"Timeout: Only {up_nodes_count} out of {expected_nodes_count} nodes " + f"reached 'Green' status within {timeout_seconds} seconds. " + f"({reported_nodes} nodes reported in last check)." + ) + if last_exception: + error_message += f" Last exception: {last_exception}" + + try: + final_nodes_info = self.swagger_client.nodes_info() + error_message += f" Final status info: {final_nodes_info}" + except Exception as final_e: + error_message += f" Could not get final status: {final_e}" + + raise TimeoutError(error_message) + + def test_migration_to_v2(self): + table_path = '/Root/mydb/mytable_migration' + number_of_tablets = 5 + + tablet_ids = create_kv_tablets_and_wait_for_start( + self.cluster.client, + self.cluster.kv_client, + self.swagger_client, + number_of_tablets, + table_path, + timeout_seconds=3 + ) + + # 1 step: fetch config with dynconfig client + fetched_config = fetch_config_dynconfig(self.dynconfig_client) + if fetched_config is None: + logger.info("No config found, generating it") + # 2 step: generate config + generated_config = generate_config(self.dynconfig_client) + parsed_generated_config = yaml.safe_load(generated_config) + metadata_section = { + "version": 0, + "cluster": "", + "kind": "MainConfig", + } + parsed_fetched_config = { + "metadata": metadata_section, + "config": parsed_generated_config + } + fetched_config = yaml.dump(parsed_fetched_config) + logger.debug(f"Generated config: {fetched_config}") + + # 3 step: add feature flag + parsed_fetched_config = yaml.safe_load(fetched_config) + parsed_fetched_config["config"]["feature_flags"] = dict() + parsed_fetched_config["config"]["feature_flags"]["switch_to_config_v2"] = True + + # 4 step: manually replace config on nodes: + self.cluster.overwrite_configs(parsed_fetched_config) + + # 5 step: use config dir + self.cluster.enable_config_dir() + + # 6 step: restart nodes + self.cluster.restart_nodes() + self.wait_for_all_nodes_start(len(self.cluster.nodes)) + + self.check_kikimr_is_operational(table_path, tablet_ids) + + logger.debug(f"Replacing config: {yaml.dump(parsed_fetched_config)}") + # 7 step: replace config + replace_config(self.config_client, yaml.dump(parsed_fetched_config)) + time.sleep(2) + + # 8 step: fetch config + fetched_config = fetch_config(self.config_client) + assert_that(fetched_config is not None) + logger.debug(f"Fetched config: {fetched_config}") + parsed_fetched_config = yaml.safe_load(fetched_config) + + # 9 step: enable self-management + parsed_fetched_config["config"]["self_management_config"] = dict() + parsed_fetched_config["config"]["self_management_config"]["enabled"] = True + parsed_fetched_config["metadata"]["version"] = 1 + + # 10 step: replace config + logger.debug(f"Replacing config: {yaml.dump(parsed_fetched_config)}") + replace_config(self.config_client, yaml.dump(parsed_fetched_config)) + + # 11 step: restart nodes + logger.debug("Restarting nodes") + self.cluster.restart_nodes() + self.wait_for_all_nodes_start(len(self.cluster.nodes)) + + self.check_kikimr_is_operational(table_path, tablet_ids) + + # 11.5 step: fetch config + logger.debug("Fetching config") + fetched_config = fetch_config(self.config_client) + assert_that(fetched_config is not None) + parsed_fetched_config = yaml.safe_load(fetched_config) + + # 12 step: move security_config to root + parsed_fetched_config["config"]["security_config"] = parsed_fetched_config["config"]["domains_config"].pop("security_config") + + # 13 step: remove unnecessary fields + parsed_fetched_config["config"].pop("domains_config") + parsed_fetched_config["config"].pop("blob_storage_config") + parsed_fetched_config["metadata"]["version"] = 2 + + # 14 step: replace config + logger.debug(f"Replacing config: {yaml.dump(parsed_fetched_config)}") + replace_config(self.config_client, yaml.dump(parsed_fetched_config)) + + self.check_kikimr_is_operational(table_path, tablet_ids) + + # 14* step: restart nodes + logger.debug("Restarting nodes") + self.cluster.restart_nodes() + self.wait_for_all_nodes_start(len(self.cluster.nodes)) + + self.check_kikimr_is_operational(table_path, tablet_ids) diff --git a/ydb/tests/functional/config/ya.make b/ydb/tests/functional/config/ya.make index 4626c8c968d1..fc98f7825cbd 100644 --- a/ydb/tests/functional/config/ya.make +++ b/ydb/tests/functional/config/ya.make @@ -4,6 +4,7 @@ TEST_SRCS( test_config_with_metadata.py test_generate_dynamic_config.py test_distconf.py + test_config_migration.py ) SPLIT_FACTOR(10) diff --git a/ydb/tests/library/clients/kikimr_dynconfig_client.py b/ydb/tests/library/clients/kikimr_dynconfig_client.py index db3ad9490905..37b18399b776 100644 --- a/ydb/tests/library/clients/kikimr_dynconfig_client.py +++ b/ydb/tests/library/clients/kikimr_dynconfig_client.py @@ -61,6 +61,10 @@ def fetch_startup_config(self): request = dynamic_config_api.FetchStartupConfigRequest() return self.invoke(request, 'FetchStartupConfig') + def fetch_config(self): + request = dynamic_config_api.GetConfigRequest() + return self.invoke(request, 'GetConfig') + def close(self): self._channel.close() diff --git a/ydb/tests/library/clients/kikimr_http_client.py b/ydb/tests/library/clients/kikimr_http_client.py index b7a42e1926f1..1fed437016f4 100644 --- a/ydb/tests/library/clients/kikimr_http_client.py +++ b/ydb/tests/library/clients/kikimr_http_client.py @@ -172,3 +172,8 @@ def tablet_info(self, tablet_type=None): return self.__http_get_and_parse_json( "/json/tabletinfo", timeout=self.__timeout ) + + def nodes_info(self): + return self.__http_get_and_parse_json( + "/json/nodes", timeout=self.__timeout + ) diff --git a/ydb/tests/library/harness/daemon.py b/ydb/tests/library/harness/daemon.py index a3e26d23ab2a..92e489408149 100644 --- a/ydb/tests/library/harness/daemon.py +++ b/ydb/tests/library/harness/daemon.py @@ -79,6 +79,12 @@ def __init__( self.__stderr_file = None self.__aux_file = None + def update_command(self, new_command): + new_command_tuple = tuple(new_command) + if self.__command != new_command_tuple: + self.logger.info(f"Updating daemon command to {new_command_tuple}") + self.__command = new_command_tuple + def __open_output_files(self): self.__stdout_file = open(self.__stdout_file_name, mode='ab') self.__stderr_file = open(self.__stderr_file_name, mode='ab') diff --git a/ydb/tests/library/harness/kikimr_config.py b/ydb/tests/library/harness/kikimr_config.py index 0581196f7cf2..9b010ac42902 100644 --- a/ydb/tests/library/harness/kikimr_config.py +++ b/ydb/tests/library/harness/kikimr_config.py @@ -170,6 +170,7 @@ def __init__( simple_config=False, breakpad_minidumps_path=None, breakpad_minidumps_script=None, + explicit_hosts_and_host_configs=False, ): if extra_feature_flags is None: extra_feature_flags = [] @@ -180,8 +181,10 @@ def __init__( self.use_self_management = use_self_management self.simple_config = simple_config self.suppress_version_check = suppress_version_check + self.explicit_hosts_and_host_configs = explicit_hosts_and_host_configs if use_self_management: self.suppress_version_check = False + self.explicit_hosts_and_host_configs = True self._pdisk_store_path = pdisk_store_path self.static_pdisk_size = static_pdisk_size self.app_config = config_pb2.TAppConfig() @@ -480,14 +483,16 @@ def __init__( self.yaml_config["kafka_proxy_config"] = kafka_proxy_config self.full_config = dict() + if self.explicit_hosts_and_host_configs: + self._add_host_config_and_hosts() + self.yaml_config.pop("nameservice_config") if self.use_self_management: self.yaml_config["domains_config"].pop("security_config") self.yaml_config["default_disk_type"] = "ROT" self.yaml_config["fail_domain_type"] = "rack" - self._add_host_config_and_hosts() self.yaml_config["erasure"] = self.yaml_config.pop("static_erasure") - for name in ['blob_storage_config', 'domains_config', 'nameservice_config', 'system_tablets', 'grpc_config', + for name in ['blob_storage_config', 'domains_config', 'system_tablets', 'grpc_config', 'channel_profile_config', 'interconnect_config']: del self.yaml_config[name] if self.simple_config: @@ -780,5 +785,5 @@ def __build(self): self.yaml_config["blob_storage_config"]["service_set"]["groups"][0]["rings"].append({"fail_domains": []}) self._add_state_storage_config() - if not self.use_self_management: + if not self.use_self_management and not self.explicit_hosts_and_host_configs: self._initialize_pdisks_info() diff --git a/ydb/tests/library/harness/kikimr_runner.py b/ydb/tests/library/harness/kikimr_runner.py index 104a095a358d..3567b67ebc52 100644 --- a/ydb/tests/library/harness/kikimr_runner.py +++ b/ydb/tests/library/harness/kikimr_runner.py @@ -290,6 +290,19 @@ def get_config_version(self): config = self.read_node_config() return config.get('metadata', {}).get('version', 0) + def enable_config_dir(self): + self.__configurator.use_config_store = True + self.update_command(self.__make_run_command()) + + def make_config_dir(self, source_config_yaml_path, target_config_dir_path): + if not os.path.exists(source_config_yaml_path): + raise FileNotFoundError(f"Source config file not found: {source_config_yaml_path}") + + try: + os.makedirs(target_config_dir_path, exist_ok=True) + except Exception as e: + raise RuntimeError(f"Unexpected error initializing config for node {self.node_id}.") from e + class KiKiMR(kikimr_cluster_interface.KiKiMRClusterInterface): def __init__(self, configurator=None, cluster_name='cluster'): @@ -363,7 +376,7 @@ def __call_kikimr_new_cli(self, cmd, connect_to_server=True, token=None): )) raise - def __call_ydb_cli(self, cmd, token=None): + def __call_ydb_cli(self, cmd, token=None, check_exit_code=True): endpoint = 'grpc://{server}:{port}'.format(server=self.server, port=self.nodes[1].port) full_command = [self.__configurator.get_ydb_cli_path(), '--endpoint', endpoint, '-y'] + cmd @@ -375,7 +388,7 @@ def __call_ydb_cli(self, cmd, token=None): logger.debug("Executing command = {}".format(full_command)) try: - return yatest.common.execute(full_command) + return yatest.common.execute(full_command, env=env, check_exit_code=check_exit_code) except yatest.common.ExecutionError as e: logger.exception("KiKiMR command '{cmd}' failed with error: {e}\n\tstdout: {out}\n\tstderr: {err}".format( cmd=" ".join(str(x) for x in full_command), @@ -623,6 +636,15 @@ def __write_configs(self): else: self.__configurator.write_proto_configs(self.__config_path) + def overwrite_configs(self, config): + self.__configurator.full_config = config + self.__write_configs() + + def enable_config_dir(self): + self.__configurator.use_config_store = True + for node in self.nodes.values(): + node.enable_config_dir() + def __instantiate_udfs_dir(self): to_load = self.__configurator.get_yql_udfs_to_load() if len(to_load) == 0: @@ -726,8 +748,8 @@ def predicate(): assert bs_controller_started def __cluster_bootstrap(self): - timeout = 240 - sleep = 5 + timeout = 10 + sleep = 2 retries, success = timeout / sleep, False while retries > 0 and not success: try: @@ -749,6 +771,59 @@ def __cluster_bootstrap(self): if retries == 0: raise + def replace_config(self, config): + timeout = 10 + sleep = 2 + retries, success = timeout / sleep, False + while retries > 0 and not success: + try: + self.__call_ydb_cli( + [ + "admin", + "cluster", + "config", + "replace", + "-f", config + ] + ) + success = True + except Exception as e: + logger.error("Failed to execute, %s", str(e)) + retries -= 1 + time.sleep(sleep) + if not success: + logger.error("Failed to replace config") + raise RuntimeError("Failed to replace config") + + def fetch_config(self): + try: + result = self.__call_ydb_cli( + [ + "admin", + "cluster", + "config", + "fetch" + ], + check_exit_code=False + ) + if result.exit_code != 0: + return None + return result.std_out.decode('utf-8') + except Exception as e: + logger.error(f"Error fetching config: {e}", exc_info=True) + return None + + def generate_config(self): + result = self.__call_ydb_cli( + [ + "admin", + "cluster", + "config", + "generate" + ] + ) + return result.std_out.decode('utf-8') + class KikimrExternalNode(daemon.ExternalNodeDaemon, kikimr_node_interface.NodeInterface): kikimr_binary_deploy_path = '/Berkanavt/kikimr/bin/kikimr' diff --git a/ydb/tests/library/harness/resources/default_domains.txt b/ydb/tests/library/harness/resources/default_domains.txt index 5c288afa9835..a1345a3f3337 100644 --- a/ydb/tests/library/harness/resources/default_domains.txt +++ b/ydb/tests/library/harness/resources/default_domains.txt @@ -1,6 +1,6 @@ Domain { DomainId: 1 - SchemeRoot: 72075186232723360 + SchemeRoot: 72057594046678944 SSId: 1 HiveUid: 1 PlanResolution: 10 diff --git a/ydb/tests/library/harness/resources/default_yaml.yml b/ydb/tests/library/harness/resources/default_yaml.yml index fc157171501d..c0358b0c9a71 100644 --- a/ydb/tests/library/harness/resources/default_yaml.yml +++ b/ydb/tests/library/harness/resources/default_yaml.yml @@ -38,7 +38,6 @@ domains_config: domain: - name: "{ydb_domain_name}" domain_id: 1 - scheme_root: 72075186232723360 storage_pool_types: - kind: "hdd" pool_config: @@ -92,7 +91,7 @@ system_tablets: - node: {ydb_defaut_tablet_node_ids} flat_schemeshard: - info: - tablet_id: 72075186232723360 + tablet_id: 72057594046678944 actor_system_config: executor: - type: BASIC