|
| 1 | +import filecmp |
1 | 2 | import os |
2 | 3 | import re |
3 | 4 | import shutil |
|
15 | 16 | run_command_and_get_output, |
16 | 17 | wait_event, |
17 | 18 | wait_file, |
| 19 | + wait_files, |
18 | 20 | ) |
19 | 21 |
|
20 | 22 | tt_config_text = """env: |
@@ -1051,3 +1053,186 @@ def select_data_func(): |
1051 | 1053 | # Assert here to be sure that instances are stopped. |
1052 | 1054 | assert can_insert, "can not insert data into the vshard cluster" |
1053 | 1055 | assert can_select, "can not select data from the vhsard cluster" |
| 1056 | + |
| 1057 | + |
| 1058 | +def check_create(tt_cmd, workdir, template, app_name, params, files): |
| 1059 | + input = "".join(["\n" if x is None else f"{x}\n" for x in params]) |
| 1060 | + create_cmd = [tt_cmd, "create", template, "--name", app_name] |
| 1061 | + p = subprocess.run( |
| 1062 | + create_cmd, |
| 1063 | + cwd=workdir, |
| 1064 | + stderr=subprocess.STDOUT, |
| 1065 | + stdout=subprocess.PIPE, |
| 1066 | + text=True, |
| 1067 | + input=input, |
| 1068 | + ) |
| 1069 | + assert p.returncode == 0 |
| 1070 | + assert f"Application '{app_name}' created successfully" in p.stdout |
| 1071 | + for f in files: |
| 1072 | + assert os.path.exists(workdir / app_name / f) |
| 1073 | + |
| 1074 | + |
| 1075 | +def get_status_info(tt_cmd, workdir, target): |
| 1076 | + status_cmd = [tt_cmd, "status", target] |
| 1077 | + p = subprocess.run( |
| 1078 | + status_cmd, |
| 1079 | + cwd=workdir, |
| 1080 | + stderr=subprocess.STDOUT, |
| 1081 | + stdout=subprocess.PIPE, |
| 1082 | + text=True, |
| 1083 | + ) |
| 1084 | + assert p.returncode == 0 |
| 1085 | + return extract_status(p.stdout) |
| 1086 | + |
| 1087 | + |
| 1088 | +def wait_for_master(tt_cmd, workdir, app_name): |
| 1089 | + def has_master(): |
| 1090 | + status_info = get_status_info(tt_cmd, workdir, app_name) |
| 1091 | + for key in status_info.keys(): |
| 1092 | + if "MODE" in status_info[key] and status_info[key]["MODE"] == "RW": |
| 1093 | + return True |
| 1094 | + return False |
| 1095 | + |
| 1096 | + return wait_event(10, has_master, 1) |
| 1097 | + |
| 1098 | + |
| 1099 | +@pytest.mark.slow |
| 1100 | +@pytest.mark.skipif( |
| 1101 | + tarantool_major_version < 3, |
| 1102 | + reason="skip centralized config test for Tarantool < 3", |
| 1103 | +) |
| 1104 | +@pytest.mark.parametrize("num_replicas", [3, 5]) |
| 1105 | +def test_create_config_storage(tt_cmd, tmp_path, num_replicas): |
| 1106 | + with open(os.path.join(tmp_path, config_name), "w") as tnt_env_file: |
| 1107 | + tnt_env_file.write(tt_config_text.format(tmp_path)) |
| 1108 | + |
| 1109 | + app_name = "app1" |
| 1110 | + files = ["config.yaml", "instances.yaml"] |
| 1111 | + instances = [f"replicaset-001-{chr(ord('a') + i)}" for i in range(num_replicas)] |
| 1112 | + |
| 1113 | + # Create app. |
| 1114 | + check_create(tt_cmd, tmp_path, "config_storage", app_name, [num_replicas] + [None] * 3, files) |
| 1115 | + |
| 1116 | + try: |
| 1117 | + # Start app. |
| 1118 | + start_cmd = [tt_cmd, "start", app_name] |
| 1119 | + p = subprocess.run( |
| 1120 | + start_cmd, |
| 1121 | + cwd=tmp_path, |
| 1122 | + stderr=subprocess.STDOUT, |
| 1123 | + stdout=subprocess.PIPE, |
| 1124 | + ) |
| 1125 | + assert p.returncode == 0 |
| 1126 | + pid_files = [os.path.join(tmp_path, app_name, inst, pid_file) for inst in instances] |
| 1127 | + assert wait_files(3, pid_files) |
| 1128 | + assert wait_for_master(tt_cmd, tmp_path, app_name) |
| 1129 | + |
| 1130 | + # Check status. |
| 1131 | + status_info = get_status_info(tt_cmd, tmp_path, app_name) |
| 1132 | + master = None |
| 1133 | + replica = None |
| 1134 | + for key in status_info.keys(): |
| 1135 | + if status_info[key]["MODE"] == "RW": |
| 1136 | + master = key |
| 1137 | + else: |
| 1138 | + replica = key |
| 1139 | + assert status_info[key]["STATUS"] == "RUNNING" |
| 1140 | + |
| 1141 | + def exec_on_inst(inst, cmd): |
| 1142 | + return subprocess.run( |
| 1143 | + [tt_cmd, "connect", inst, "-f-"], |
| 1144 | + cwd=tmp_path, |
| 1145 | + stderr=subprocess.STDOUT, |
| 1146 | + stdout=subprocess.PIPE, |
| 1147 | + text=True, |
| 1148 | + input=cmd, |
| 1149 | + ) |
| 1150 | + |
| 1151 | + def write_data_func(inst): |
| 1152 | + def f(): |
| 1153 | + p = exec_on_inst(inst, "config.storage.put('/a', 'some value')") |
| 1154 | + return p.returncode == 0 and "revision" in p.stdout |
| 1155 | + |
| 1156 | + return f |
| 1157 | + |
| 1158 | + def read_data_func(inst): |
| 1159 | + def f(): |
| 1160 | + p = exec_on_inst(inst, "config.storage.get('/a')") |
| 1161 | + return p.returncode == 0 and "some value" in p.stdout |
| 1162 | + |
| 1163 | + return f |
| 1164 | + |
| 1165 | + # Check read/write. |
| 1166 | + assert wait_event(3, write_data_func(f"{master}")), ( |
| 1167 | + f"can not write data to the master instance '${master}'" |
| 1168 | + ) |
| 1169 | + assert not wait_event(3, write_data_func(f"{replica}")), ( |
| 1170 | + f"unexpectedly write data to the replica instance '${replica}'" |
| 1171 | + ) |
| 1172 | + assert wait_event(3, read_data_func(f"{master}")), ( |
| 1173 | + f"can not read data from the master instance '${master}'" |
| 1174 | + ) |
| 1175 | + assert wait_event(3, read_data_func(f"{replica}")), ( |
| 1176 | + f"can not read data from the replica instance '${replica}'" |
| 1177 | + ) |
| 1178 | + |
| 1179 | + finally: |
| 1180 | + # Stop app. |
| 1181 | + stop_cmd = [tt_cmd, "stop", "--yes", app_name] |
| 1182 | + p = subprocess.run( |
| 1183 | + stop_cmd, |
| 1184 | + cwd=tmp_path, |
| 1185 | + stderr=subprocess.STDOUT, |
| 1186 | + stdout=subprocess.PIPE, |
| 1187 | + text=True, |
| 1188 | + ) |
| 1189 | + assert p.returncode == 0 |
| 1190 | + |
| 1191 | + |
| 1192 | +@pytest.mark.skipif( |
| 1193 | + tarantool_major_version < 3, |
| 1194 | + reason="skip centralized config test for Tarantool < 3", |
| 1195 | +) |
| 1196 | +@pytest.mark.parametrize( |
| 1197 | + "template", |
| 1198 | + [ |
| 1199 | + "cartridge", |
| 1200 | + "vshard_cluster", |
| 1201 | + "config_storage", |
| 1202 | + ], |
| 1203 | +) |
| 1204 | +def test_create_builtin_template_with_defaults(tt_cmd, tmp_path, template): |
| 1205 | + with open(os.path.join(tmp_path, config_name), "w") as tnt_env_file: |
| 1206 | + tnt_env_file.write(tt_config_text.format(tmp_path)) |
| 1207 | + |
| 1208 | + templates_data = { |
| 1209 | + "cartridge": { |
| 1210 | + "default_params": ["secret-cluster-cookie"], |
| 1211 | + "parametrizable_files": ["init.lua"], |
| 1212 | + }, |
| 1213 | + "vshard_cluster": { |
| 1214 | + "default_params": [3000, 2, 2, 1], |
| 1215 | + "parametrizable_files": ["config.yaml", "instances.yaml"], |
| 1216 | + }, |
| 1217 | + "config_storage": { |
| 1218 | + "default_params": [3, 5, "client", "secret"], |
| 1219 | + "parametrizable_files": ["config.yaml", "instances.yaml"], |
| 1220 | + }, |
| 1221 | + } |
| 1222 | + files = templates_data[template]["parametrizable_files"] |
| 1223 | + |
| 1224 | + # Create reference app (default values are specified explicitly). |
| 1225 | + ref_app_name = "ref_app" |
| 1226 | + ref_params = templates_data[template]["default_params"] |
| 1227 | + check_create(tt_cmd, tmp_path, template, ref_app_name, ref_params, files) |
| 1228 | + |
| 1229 | + # Create default app (no values, just continuously pressing enter to accept defaults). |
| 1230 | + default_app_name = "default_app" |
| 1231 | + default_params = [None] * len(ref_params) |
| 1232 | + check_create(tt_cmd, tmp_path, template, default_app_name, default_params, files) |
| 1233 | + |
| 1234 | + # Check that the corresponding files are identical. |
| 1235 | + for f in files: |
| 1236 | + default_path = tmp_path / default_app_name / f |
| 1237 | + ref_path = tmp_path / ref_app_name / f |
| 1238 | + assert filecmp.cmp(default_path, ref_path, shallow=False) |
0 commit comments