Skip to content

Commit af5ad67

Browse files
authored
Add yaml config support to ydbd_slice (#1406)
* add yaml config support to ydbd_slice * remove nested dir for dynconfig
1 parent d030920 commit af5ad67

File tree

6 files changed

+245
-39
lines changed

6 files changed

+245
-39
lines changed

ydb/tools/ydbd_slice/__init__.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from ydb.tools.ydbd_slice import nodes, handlers, cluster_description
1414
from ydb.tools.ydbd_slice.kube import handlers as kube_handlers, docker
1515

16-
1716
warnings.filterwarnings("ignore", category=DeprecationWarning)
1817
warnings.filterwarnings("ignore", category=HTTPWarning)
1918

@@ -830,7 +829,7 @@ def _run(args):
830829

831830
manifests = kube_handlers.get_all_manifests(args.path)
832831
kube_handlers.manifests_ydb_set_image(args.path, manifests, image)
833-
kube_handlers.slice_install(args.path, manifests, args.wait_ready)
832+
kube_handlers.slice_install(args.path, manifests, args.wait_ready, args.dynamic_config_type)
834833

835834
logger.info('kube-install finished')
836835
except RuntimeError as e:
@@ -857,6 +856,12 @@ def _run(args):
857856
help='Do not build docker image, just specify image name in manifests.',
858857
action='store_true',
859858
)
859+
mode.add_argument(
860+
'--dynamic-config-type',
861+
help='Upload dynamic config with specified type',
862+
choices=['both', 'proto', 'yaml', 'none'],
863+
default='both',
864+
)
860865
add_arguments_docker_build_with_remainder(mode, add_force_rebuild=True)
861866
mode.set_defaults(handler=_run)
862867

@@ -872,7 +877,7 @@ def _run(args):
872877
manifests = kube_handlers.get_all_manifests(args.path)
873878
manifests = kube_handlers.manifests_ydb_filter_components(args.path, manifests, args.components)
874879
kube_handlers.manifests_ydb_set_image(args.path, manifests, image)
875-
kube_handlers.slice_update(args.path, manifests, args.wait_ready)
880+
kube_handlers.slice_update(args.path, manifests, args.wait_ready, args.dynamic_config_type)
876881

877882
logger.info('kube-update finished')
878883
except RuntimeError as e:
@@ -905,6 +910,12 @@ def _run(args):
905910
help='Do not build docker image, just specify image name in manifests.',
906911
action='store_true',
907912
)
913+
mode.add_argument(
914+
'--dynamic-config-type',
915+
help='Upload dynamic config with specified type',
916+
choices=['both', 'proto', 'yaml', 'none'],
917+
default='both',
918+
)
908919
add_arguments_docker_build_with_remainder(mode, add_force_rebuild=True)
909920
mode.set_defaults(handler=_run)
910921

@@ -947,7 +958,7 @@ def _run(args):
947958
try:
948959
manifests = kube_handlers.get_all_manifests(args.path)
949960
manifests = kube_handlers.manifests_ydb_filter_components(args.path, manifests, args.components)
950-
kube_handlers.slice_start(args.path, manifests, args.wait_ready)
961+
kube_handlers.slice_start(args.path, manifests, args.wait_ready, args.dynamic_config_type)
951962

952963
logger.info('kube-start finished')
953964
except RuntimeError as e:
@@ -975,6 +986,12 @@ def _run(args):
975986
help='Wait for ydb objects ready state. Default: false',
976987
action='store_true',
977988
)
989+
mode.add_argument(
990+
'--dynamic-config-type',
991+
help='Upload dynamic config with specified type',
992+
choices=['both', 'proto', 'yaml', 'none'],
993+
default='both',
994+
)
978995
mode.set_defaults(handler=_run)
979996

980997

@@ -1040,7 +1057,7 @@ def _run(args):
10401057
logger.debug("starting kube-format cmd with args '%s'", args)
10411058
try:
10421059
manifests = kube_handlers.get_all_manifests(args.path)
1043-
kube_handlers.slice_format(args.path, manifests, args.wait_ready)
1060+
kube_handlers.slice_format(args.path, manifests, args.wait_ready, args.dynamic_config_type)
10441061

10451062
logger.info('kube-format finished')
10461063
except RuntimeError as e:
@@ -1063,6 +1080,12 @@ def _run(args):
10631080
help='Wait for ydb objects ready state. Default: false',
10641081
action='store_true',
10651082
)
1083+
mode.add_argument(
1084+
'--dynamic-config-type',
1085+
help='Upload dynamic config with specified type',
1086+
choices=['both', 'proto', 'yaml', 'none'],
1087+
default='both',
1088+
)
10661089
mode.set_defaults(handler=_run)
10671090

10681091

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# small wrapper for operations with dynconfig
2+
3+
import logging
4+
import os
5+
6+
from ydb import Driver as YdbDriver, DriverConfig, AnonymousCredentials, credentials_from_env_variables
7+
from ydb.draft import DynamicConfigClient
8+
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
DYNCONFIG_NAME= 'dynconfig.yaml'
14+
15+
16+
class Client():
17+
def __init__(self, node_list, domain, credentials=credentials_from_env_variables(), allow_fallback_to_anonymous_credentials=True):
18+
self.config = DriverConfig(
19+
endpoint=f'grpc://{node_list[0]}:2135',
20+
database=domain,
21+
credentials=credentials,
22+
endpoints=[f'grpc://{x}:2135' for x in node_list],
23+
)
24+
self.allow_fallback_to_anonymous_credentials = allow_fallback_to_anonymous_credentials
25+
self.driver = YdbDriver(self.config)
26+
27+
def __enter__(self):
28+
try:
29+
self.driver.wait(timeout=5)
30+
except TimeoutError:
31+
if self.allow_fallback_to_anonymous_credentials:
32+
logger.warning('Trying to fallback to anonymous credentials. ' \
33+
'To get rid of this message either set YDB_ANONYMOUS_CREDENTIALS=1 ' \
34+
'Or set correct credentials in env as it described in ydb-python-sdk.')
35+
config = self.config
36+
config.credentials = AnonymousCredentials()
37+
self.driver = YdbDriver(config)
38+
try:
39+
self.driver.wait(timeout=5)
40+
except TimeoutError:
41+
logger.error('Unable to connect to static nodes for dynconfig management.')
42+
raise
43+
else:
44+
logger.error('Unable to connect to static nodes for dynconfig management.')
45+
raise
46+
47+
return DynamicConfigClient(self.driver)
48+
49+
def __exit__(self, exception_type, exception_value, exception_traceback):
50+
pass
51+
52+
53+
def get_config_path(project_path):
54+
return os.path.join(project_path, DYNCONFIG_NAME)
55+
56+
57+
def get_config_abspath(project_path):
58+
return os.path.abspath(get_config_path(project_path))
59+
60+
61+
def get_local_config(project_path):
62+
dynconfig_file = get_config_abspath(project_path)
63+
if not os.path.exists(dynconfig_file):
64+
return None
65+
with open(dynconfig_file, 'r') as config:
66+
return config.read()
67+
68+
69+
def write_local_config(project_path, new_config):
70+
dynconfig_file = get_config_abspath(project_path)
71+
with open(dynconfig_file, 'w') as config:
72+
config.write(new_config)
73+
74+
75+
def get_remote_config(client):
76+
return client.get_config().config
77+
78+
79+
def apply_config(client, project_path, force=False, allow_unknown_fields=True):
80+
config = get_local_config(project_path)
81+
if force:
82+
client.set_config(config, False, allow_unknown_fields)
83+
else:
84+
client.replace_config(config, False, allow_unknown_fields)
85+
# TODO: there is small race here, we can pull back config applied after our's, but for dev slices it's fine
86+
# as far as even in real production with long approve process we faced conflict only once in a three months
87+
return get_remote_config(client)

ydb/tools/ydbd_slice/kube/generate.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
import jinja2
44
import library.python.resource as rs
55

6-
from ydb.tools.ydbd_slice.kube import cms
6+
from ydb.tools.ydbd_slice.kube import cms, dynconfig
77

88

99
def render_resource(name, params):
1010
env = jinja2.Environment(
1111
loader=jinja2.FunctionLoader(lambda x: rs.find(x).decode()), undefined=jinja2.StrictUndefined
1212
)
13+
1314
template = env.get_template(name)
1415
return template.render(**params)
1516

@@ -50,8 +51,20 @@ def generate_legacy_configs(project_path, preferred_pool_kind='ssd'):
5051
)
5152

5253

54+
def generate_dynconfigs(project_path, namespace_name, cluster_uuid, preferred_pool_kind='ssd'):
55+
generate_file(
56+
project_path=project_path,
57+
filename=dynconfig.get_config_path(project_path),
58+
template='/ydbd_slice/templates/common/dynconfig.yaml',
59+
template_kwargs=dict(
60+
preferred_pool_kind=preferred_pool_kind,
61+
cluster_uuid=cluster_uuid,
62+
)
63+
)
64+
65+
5366
def generate_8_node_block_4_2(project_path, user, namespace_name, nodeclaim_name, node_flavor,
54-
storage_name, database_name):
67+
storage_name, database_name, cluster_uuid=''):
5568
generate_file(
5669
project_path=project_path,
5770
filename=f'namespace-{namespace_name}.yaml',
@@ -95,4 +108,6 @@ def generate_8_node_block_4_2(project_path, user, namespace_name, nodeclaim_name
95108
)
96109
)
97110

111+
generate_dynconfigs(project_path, namespace_name, cluster_uuid)
112+
98113
generate_legacy_configs(project_path)

ydb/tools/ydbd_slice/kube/handlers.py

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
import os
33
import sys
44
import logging
5-
5+
import time
66

77
from collections import defaultdict
88
from kubernetes.client import Configuration
99

1010
from ydb.tools.ydbd_slice import nodes, handlers
11-
from ydb.tools.ydbd_slice.kube import api, kubectl, yaml, generate, cms
11+
from ydb.tools.ydbd_slice.kube import api, kubectl, yaml, generate, cms, dynconfig
1212

1313

1414
logger = logging.getLogger(__name__)
@@ -148,6 +148,13 @@ def get_nodes(api_client, project_path, manifests):
148148
return node_list
149149

150150

151+
def get_domain(api_client, project_path, manifests):
152+
for (_, _, kind, _, _, data) in manifests:
153+
if kind != 'storage':
154+
continue
155+
return data['spec']['domain']
156+
157+
151158
def manifests_ydb_set_image(project_path, manifests, image):
152159
for (path, api_version, kind, namespace, name, data) in manifests:
153160
if not (kind in ['storage', 'database'] and api_version in ['ydb.tech/v1alpha1']):
@@ -245,9 +252,20 @@ def slice_nodeclaim_delete(api_client, project_path, manifests):
245252
sys.exit(e.args[0])
246253

247254

255+
def wait_for_storage(api_client, project_path, manifests):
256+
for (path, api_version, kind, namespace, name, data) in manifests:
257+
if not (kind in ['storage'] and api_version in ['ydb.tech/v1alpha1']):
258+
continue
259+
namespace = data['metadata']['namespace']
260+
name = data['metadata']['name']
261+
try:
262+
api.wait_storage_state_ready(api_client, namespace, name)
263+
except TimeoutError as e:
264+
sys.exit(e.args[0])
265+
248266
#
249267
# macro level ydb functions
250-
def slice_ydb_apply(api_client, project_path, manifests):
268+
def slice_ydb_apply(api_client, project_path, manifests, dynamic_config_type):
251269
# process storages first
252270
for (path, api_version, kind, namespace, name, data) in manifests:
253271
if not (kind in ['storage', 'database'] and api_version in ['ydb.tech/v1alpha1']):
@@ -264,28 +282,36 @@ def slice_ydb_apply(api_client, project_path, manifests):
264282
data['spec'] = new_data['spec']
265283
update_manifest(path, data)
266284

267-
config_items = cms.get_from_files(project_path)
268-
if config_items is not None:
269-
logger.debug(
270-
f'found {len(config_items)} legacy cms config items, '
271-
'need to wait for storage to become ready to apply configs'
272-
)
273-
# if configs present, then wait for storage
274-
for (path, api_version, kind, namespace, name, data) in manifests:
275-
if not (kind in ['storage'] and api_version in ['ydb.tech/v1alpha1']):
276-
continue
277-
namespace = data['metadata']['namespace']
278-
name = data['metadata']['name']
279-
try:
280-
api.wait_storage_state_ready(api_client, namespace, name)
281-
except TimeoutError as e:
282-
sys.exit(e.args[0])
285+
if dynamic_config_type in ['both', 'yaml']:
286+
local_config = dynconfig.get_local_config(project_path)
287+
if local_config is not None:
288+
wait_for_storage(api_client, project_path, manifests)
289+
290+
node_list = get_nodes(api_client, project_path, manifests)
291+
domain = get_domain(api_client, project_path, manifests)
292+
293+
with dynconfig.Client(node_list, domain) as dynconfig_client:
294+
remote_config = dynconfig.get_remote_config(dynconfig_client)
295+
296+
if remote_config is None or remote_config != local_config:
297+
new_config = dynconfig.apply_config(dynconfig_client, project_path)
298+
dynconfig.write_local_config(project_path, new_config)
299+
300+
if dynamic_config_type in ['both', 'proto']:
301+
config_items = cms.get_from_files(project_path)
302+
if config_items is not None:
303+
logger.debug(
304+
f'found {len(config_items)} legacy cms config items, '
305+
'need to wait for storage to become ready to apply configs'
306+
)
307+
# if configs present, then wait for storage
308+
wait_for_storage(api_client, project_path, manifests)
283309

284-
# and apply configs
285-
node_list = get_nodes(api_client, project_path, manifests)
286-
if len(node_list) == 0:
287-
raise RuntimeError('no nodes found, cannot apply legacy cms config items.')
288-
cms.apply_legacy_cms_config_items(config_items, [f'grpc://{i}:2135' for i in node_list])
310+
# and apply configs
311+
node_list = get_nodes(api_client, project_path, manifests)
312+
if len(node_list) == 0:
313+
raise RuntimeError('no nodes found, cannot apply legacy cms config items.')
314+
cms.apply_legacy_cms_config_items(config_items, [f'grpc://{i}:2135' for i in node_list])
289315

290316
# process databases later
291317
for (path, api_version, kind, namespace, name, data) in manifests:
@@ -432,23 +458,23 @@ def slice_generate(project_path, user, slice_name, template, template_vars):
432458
sys.exit(f'Slice template {template} not implemented.')
433459

434460

435-
def slice_install(project_path, manifests, wait_ready):
461+
def slice_install(project_path, manifests, wait_ready, dynamic_config_type):
436462
with api.ApiClient() as api_client:
437463
slice_namespace_apply(api_client, project_path, manifests)
438464
slice_nodeclaim_apply(api_client, project_path, manifests)
439465
slice_nodeclaim_wait_ready(api_client, project_path, manifests)
440466
slice_ydb_delete(api_client, project_path, manifests)
441467
slice_ydb_storage_wait_pods_deleted(api_client, project_path, manifests)
442468
slice_nodeclaim_format(api_client, project_path, manifests)
443-
slice_ydb_apply(api_client, project_path, manifests)
469+
slice_ydb_apply(api_client, project_path, manifests, dynamic_config_type)
444470
slice_ydb_wait_ready(api_client, project_path, manifests, wait_ready)
445471

446472

447-
def slice_update(project_path, manifests, wait_ready):
473+
def slice_update(project_path, manifests, wait_ready, dynamic_config_type):
448474
with api.ApiClient() as api_client:
449475
slice_nodeclaim_apply(api_client, project_path, manifests)
450476
slice_nodeclaim_wait_ready(api_client, project_path, manifests)
451-
slice_ydb_apply(api_client, project_path, manifests)
477+
slice_ydb_apply(api_client, project_path, manifests, dynamic_config_type)
452478
slice_ydb_restart(api_client, project_path, manifests)
453479
slice_ydb_wait_ready(api_client, project_path, manifests, wait_ready)
454480

@@ -458,9 +484,9 @@ def slice_stop(project_path, manifests):
458484
slice_ydb_delete(api_client, project_path, manifests)
459485

460486

461-
def slice_start(project_path, manifests, wait_ready):
487+
def slice_start(project_path, manifests, wait_ready, dynamic_config_type):
462488
with api.ApiClient() as api_client:
463-
slice_ydb_apply(api_client, project_path, manifests)
489+
slice_ydb_apply(api_client, project_path, manifests, dynamic_config_type)
464490
slice_ydb_wait_ready(api_client, project_path, manifests, wait_ready)
465491

466492

@@ -474,12 +500,12 @@ def slice_nodes(project_path, manifests):
474500
slice_nodeclaim_nodes(api_client, project_path, manifests)
475501

476502

477-
def slice_format(project_path, manifests, wait_ready):
503+
def slice_format(project_path, manifests, wait_ready, dynamic_config_type):
478504
with api.ApiClient() as api_client:
479505
slice_ydb_delete(api_client, project_path, manifests)
480506
slice_ydb_storage_wait_pods_deleted(api_client, project_path, manifests)
481507
slice_nodeclaim_format(api_client, project_path, manifests)
482-
slice_ydb_apply(api_client, project_path, manifests)
508+
slice_ydb_apply(api_client, project_path, manifests, dynamic_config_type)
483509
slice_ydb_wait_ready(api_client, project_path, manifests, wait_ready)
484510

485511

0 commit comments

Comments
 (0)