Skip to content

Commit 647acef

Browse files
committed
create: add config_storage template
Closes #1180 @TarantoolBot document Title: `tt create` support `config_storage` template. Configurable parameters: - Number of instances in replicaset - Interval of status checking
1 parent 32ed4d0 commit 647acef

File tree

9 files changed

+294
-3
lines changed

9 files changed

+294
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Added
1111

12+
- `tt create`: add `config_storage` template.
13+
1214
### Changed
1315

1416
### Fixed

cli/cmd/create.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func NewCreateCmd() *cobra.Command {
4444
Built-in templates:
4545
cartridge: a simple Cartridge application.
4646
single_instance: Tarantool 3 application with a single instance configuration.
47-
vshard_cluster: Tarantool 3 vshard cluster application.`,
47+
vshard_cluster: Tarantool 3 vshard cluster application.
48+
config_storage: Tarantool 3 cluster configuration storage.`,
4849
Example: `
4950
# Create an application app1 from a template.
5051
@@ -62,7 +63,11 @@ Built-in templates:
6263
6364
# Create Tarantool 3 vshard cluster.
6465
65-
$ tt create vshard_cluster --name cluster_app`,
66+
$ tt create vshard_cluster --name cluster_app
67+
68+
# Create Tarantool 3 cluster configuration storage.
69+
70+
$ tt create config_storage --name tcs`,
6671
}
6772

6873
createCmd.Flags().StringVarP(&appName, "name", "n", "", "Application name")

cli/cmd/create_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestCreateValidArgsFunction(t *testing.T) {
4141
"cartridge",
4242
"vshard_cluster",
4343
"single_instance",
44+
"config_storage",
4445
"archive",
4546
"template2",
4647
tdir1Name,

cli/codegen/generate_code.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ func main() {
136136
if err != nil {
137137
log.Errorf("error while generating file modes: %s", err)
138138
}
139+
err = generateFileModeFile(
140+
"cli/create/builtin_templates/templates/config_storage",
141+
"cli/create/builtin_templates/static/config_storage_template_filemodes_gen.go",
142+
"ConfigStorage",
143+
)
144+
if err != nil {
145+
log.Errorf("error while generating file modes: %s", err)
146+
}
139147

140148
if err = generateLuaCodeVar(); err != nil {
141149
log.Errorf("error while generating lua code string variables: %s", err)

cli/create/builtin_templates/builtin_templates.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ var FileModes = map[string]map[string]int{
1414
"cartridge": static.CartridgeFileModes,
1515
"vshard_cluster": static.VshardClusterFileModes,
1616
"single_instance": static.SingleInstanceFileModes,
17+
"config_storage": static.ConfigStorageFileModes,
1718
}
1819

1920
// Names contains built-in template names.
20-
var Names = [...]string{"cartridge", "vshard_cluster", "single_instance"}
21+
var Names = [...]string{
22+
"cartridge",
23+
"vshard_cluster",
24+
"single_instance",
25+
"config_storage",
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
description: Config storage template
2+
follow-up-message: |
3+
What's next?
4+
Start '{{ .name }}' application:
5+
$ tt start {{ .name }}
6+
7+
Pay attention that default passwords were generated,
8+
you can change it in the config.yaml.
9+
10+
vars:
11+
- prompt: Storages replicas (odd, >=3)
12+
name: replicas_count
13+
default: '3'
14+
re: ^([3579]|[1-9]\d*[13579])$
15+
16+
- prompt: Status check interval
17+
name: status_check_interval
18+
default: '5'
19+
re: ^[1-9]\d*$
20+
21+
- prompt: User name
22+
name: username
23+
default: 'client'
24+
re: ^[\w-_0-9]+$
25+
26+
- prompt: Password
27+
name: password
28+
default: 'secret'
29+
re: ^.+$
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
credentials:
2+
users:
3+
replicator:
4+
password: 'topsecret'
5+
roles: [replication]
6+
{{.username}}:
7+
password: '{{.password}}'
8+
privileges:
9+
- permissions: [execute]
10+
lua_call:
11+
- config.storage.get
12+
- config.storage.put
13+
- config.storage.delete
14+
- config.storage.keepalive
15+
- config.storage.txn
16+
- config.storage.info
17+
# Not necessary since tarantool 3.5.0, 3.4.1, 3.3.3, 3.2.2.
18+
- permissions: [read, write]
19+
spaces: [config_storage, config_storage_meta]
20+
21+
iproto:
22+
advertise:
23+
peer:
24+
login: replicator
25+
26+
replication:
27+
failover: election
28+
29+
database:
30+
use_mvcc_engine: true
31+
32+
groups:
33+
group-001:
34+
replicasets:
35+
{{- $status_check_interval := atoi .status_check_interval}}
36+
{{- $replicas := atoi .replicas_count}}
37+
{{- range replicasets "replicaset" 1 $replicas}}
38+
{{.Name}}:
39+
roles: [config.storage]
40+
roles_cfg:
41+
config.storage:
42+
status_check_interval: {{$status_check_interval}}
43+
instances:
44+
{{- range .InstNames}}
45+
{{.}}:
46+
iproto:
47+
listen:
48+
- uri: 127.0.0.1:{{port}}
49+
{{- end}}
50+
{{- end}}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
{{- $replicas := atoi .replicas_count}}
3+
{{- range replicasets "replicaset" 1 $replicas}}
4+
{{- range .InstNames}}
5+
{{.}}:
6+
{{- end}}
7+
{{- end}}

test/integration/create/test_create.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import filecmp
12
import os
23
import re
34
import shutil
@@ -15,6 +16,7 @@
1516
run_command_and_get_output,
1617
wait_event,
1718
wait_file,
19+
wait_files,
1820
)
1921

2022
tt_config_text = """env:
@@ -1051,3 +1053,184 @@ def select_data_func():
10511053
# Assert here to be sure that instances are stopped.
10521054
assert can_insert, "can not insert data into the vshard cluster"
10531055
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+
return f
1156+
1157+
def read_data_func(inst):
1158+
def f():
1159+
p = exec_on_inst(inst, "config.storage.get('/a')")
1160+
return p.returncode == 0 and "some value" in p.stdout
1161+
return f
1162+
1163+
# Check read/write.
1164+
assert wait_event(3, write_data_func(f"{master}")), (
1165+
f"can not write data to the master instance '${master}'"
1166+
)
1167+
assert not wait_event(3, write_data_func(f"{replica}")), (
1168+
f"unexpectedly write data to the replica instance '${replica}'"
1169+
)
1170+
assert wait_event(3, read_data_func(f"{master}")), (
1171+
f"can not read data from the master instance '${master}'"
1172+
)
1173+
assert wait_event(3, read_data_func(f"{replica}")), (
1174+
f"can not read data from the replica instance '${replica}'"
1175+
)
1176+
1177+
finally:
1178+
# Stop app.
1179+
stop_cmd = [tt_cmd, "stop", "--yes", app_name]
1180+
p = subprocess.run(
1181+
stop_cmd,
1182+
cwd=tmp_path,
1183+
stderr=subprocess.STDOUT,
1184+
stdout=subprocess.PIPE,
1185+
text=True,
1186+
)
1187+
assert p.returncode == 0
1188+
1189+
1190+
@pytest.mark.skipif(
1191+
tarantool_major_version < 3,
1192+
reason="skip centralized config test for Tarantool < 3",
1193+
)
1194+
@pytest.mark.parametrize(
1195+
"template",
1196+
[
1197+
"cartridge",
1198+
"vshard_cluster",
1199+
"config_storage",
1200+
]
1201+
)
1202+
def test_create_builtin_template_with_defaults(tt_cmd, tmp_path, template):
1203+
with open(os.path.join(tmp_path, config_name), "w") as tnt_env_file:
1204+
tnt_env_file.write(tt_config_text.format(tmp_path))
1205+
1206+
templates_data = {
1207+
"cartridge": {
1208+
"default_params": ["secret-cluster-cookie"],
1209+
"parametrizable_files": ["init.lua"],
1210+
},
1211+
"vshard_cluster": {
1212+
"default_params": [3000, 2, 2, 1],
1213+
"parametrizable_files": ["config.yaml", "instances.yaml"],
1214+
},
1215+
"config_storage": {
1216+
"default_params": [3, 5, "client", "secret"],
1217+
"parametrizable_files": ["config.yaml", "instances.yaml"],
1218+
},
1219+
}
1220+
files = templates_data[template]["parametrizable_files"]
1221+
1222+
# Create reference app (default values are specified explicitly).
1223+
ref_app_name = "ref_app"
1224+
ref_params = templates_data[template]["default_params"]
1225+
check_create(tt_cmd, tmp_path, template, ref_app_name, ref_params, files)
1226+
1227+
# Create default app (no values, just continuously pressing enter to accept defaults).
1228+
default_app_name = "default_app"
1229+
default_params = [None] * len(ref_params)
1230+
check_create(tt_cmd, tmp_path, template, default_app_name, default_params, files)
1231+
1232+
# Check that the corresponding files are identical.
1233+
for f in files:
1234+
default_path = tmp_path / default_app_name / f
1235+
ref_path = tmp_path / ref_app_name / f
1236+
assert filecmp.cmp(default_path, ref_path, shallow=False)

0 commit comments

Comments
 (0)