diff options
author | Олег <150132506+iddqdex@users.noreply.github.com> | 2025-07-25 23:57:41 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-07-25 23:57:41 +0300 |
commit | ff32dad4c0a4ac13a90dfb44fdd19a5454cfe13e (patch) | |
tree | cb906af035c953af32e06c73e1eebbbd1751d3cd | |
parent | 1205e71f0d50577d63276ba84b6ef78df7763903 (diff) | |
download | ydb-main.tar.gz |
5 files changed, 236 insertions, 33 deletions
diff --git a/.github/workflows/compare_configs.yml b/.github/workflows/compare_configs.yml new file mode 100644 index 00000000000..1414401fa0a --- /dev/null +++ b/.github/workflows/compare_configs.yml @@ -0,0 +1,55 @@ +name: Compare ydb configs in branches +on: + schedule: + - cron: "0 * * * *" # Every hour + workflow_dispatch: + inputs: + commit_sha: + type: string + default: "" + +defaults: + run: + shell: bash +jobs: + main: + name: Compare configs + runs-on: [ self-hosted, auto-provisioned, build-preset-analytic-node] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 1 + - name: Setup ydb access + uses: ./.github/actions/setup_ci_ydb_service_account_key_file_credentials + with: + ci_ydb_service_account_key_file_credentials: ${{ secrets.CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS }} + - name: Build + uses: ./.github/actions/build_and_test_ya + with: + build_preset: "release" + build_target: "ydb/tests/library/compatibility/configs ydb/tests/library/compatibility/configs/comparator" + increment: false + run_tests: false + put_build_results_to_cache: false + secs: ${{ format('{{"AWS_KEY_ID":"{0}","AWS_KEY_VALUE":"{1}","REMOTE_CACHE_USERNAME":"{2}","REMOTE_CACHE_PASSWORD":"{3}"}}', + secrets.AWS_KEY_ID, secrets.AWS_KEY_VALUE, secrets.REMOTE_CACHE_USERNAME, secrets.REMOTE_CACHE_PASSWORD ) }} + vars: ${{ format('{{"AWS_BUCKET":"{0}","AWS_ENDPOINT":"{1}","REMOTE_CACHE_URL":"{2}","TESTMO_URL":"{3}","TESTMO_PROJECT_ID":"{4}"}}', + vars.AWS_BUCKET, vars.AWS_ENDPOINT, vars.REMOTE_CACHE_URL_YA, vars.TESTMO_URL, vars.TESTMO_PROJECT_ID ) }} + - name: Setup s3cmd + uses: ./.github/actions/s3cmd + with: + s3_bucket: "ydb-builds" + s3_endpoint: ${{ vars.AWS_ENDPOINT }} + s3_key_id: ${{ secrets.AWS_KEY_ID }} + s3_key_secret: ${{ secrets.AWS_KEY_VALUE }} + + - name: Comapare and publish result + shell: bash + run: | + set -xe + cd ./ydb/tests/library/compatibility/configs + ./comparator/comparator stable-25-1 stable-25-1-1 stable-25-1-2 stable-25-1-3 current >config_diff.html + s3cmd sync --follow-symlinks --acl-public --no-progress --stats --no-check-md5 "config_diff.html" "s3://ydb-builds/main/config_diff.html" -d + diff --git a/ydb/tests/library/compatibility/binaries/downloader/__main__.py b/ydb/tests/library/compatibility/binaries/downloader/__main__.py index f5b893b4043..78bee56343e 100644 --- a/ydb/tests/library/compatibility/binaries/downloader/__main__.py +++ b/ydb/tests/library/compatibility/binaries/downloader/__main__.py @@ -21,15 +21,15 @@ def main(): s3_bucket = AWS_BUCKET remote_src = sys.argv[2] local_dst = sys.argv[3] - binary_name = sys.argv[4] + binary_name = sys.argv[4] if len(sys.argv) > 4 else None s3_client.download_file(s3_bucket, remote_src, local_dst) # chmod +x st = os.stat(local_dst) os.chmod(local_dst, st.st_mode | stat.S_IEXEC) - - with open(local_dst + "-name", "w") as f: - f.write(binary_name) + if binary_name: + with open(local_dst + "-name", "w") as f: + f.write(binary_name) elif mode == 'append-version': local_dst = sys.argv[2] binary_name = sys.argv[3] diff --git a/ydb/tests/library/compatibility/configs/comparator/__main__.py b/ydb/tests/library/compatibility/configs/comparator/__main__.py new file mode 100644 index 00000000000..d0522607fe4 --- /dev/null +++ b/ydb/tests/library/compatibility/configs/comparator/__main__.py @@ -0,0 +1,132 @@ +#! /usr/bin/python3 + +from __future__ import annotations +import json +import logging +import sys +import os +from enum import StrEnum + + +class Resolution(StrEnum): + NO = '' + OK = '#aaffaa' + INFO = '#aaffaa' + WARNING = '#ffffaa' + ERROR = '#ffaaaa' + + +class FieldInfo: + def __init__(self, field: dict): + self.id = field.get('id') + self.value = field.get('default-value') + + +class Differ: + + def __init__(self): + self.fields: list[tuple[str, list[FieldInfo]]] = [] + self.resolutions: list[tuple[str, list[tuple[Resolution, str]]]] = [] + self.names: list[str] = [] + + def load_files(self, names: list[str]): + configs = [] + for name in names: + with open(name) as f: + configs.append(json.load(f).get('proto')) + self.names.append(os.path.basename(name)) + self._add_fields_dict(configs, []) + + def _add_fields_dict(self, fields: list[dict], path: list[str]): + keys = set() + for f in fields: + if isinstance(f, dict): + for key in f.keys(): + keys.add(key) + + for k in sorted(keys): + key_fields = [f.get(k) if isinstance(f, dict) else None for f in fields] + self._add_fields(key_fields, path + [k]) + + def _add_fields(self, fields: list[dict], path: list[str]): + maxlist = 0 + dicts = [] + infos = [] + for f in fields: + if f is None: + infos.append(None) + dicts.append(None) + continue + info = FieldInfo(f) + infos.append(info) + dicts.append(info.value if isinstance(info.value, dict) else None) + if isinstance(info.value, list): + maxlist = max(maxlist, len(info.value)) + self.fields.append(('.'.join(path), infos)) + self._add_fields_dict(dicts, path) + for i in range(maxlist): + index_fields = [info.value[i] if info and isinstance(info.value, list) and len(info.value) > i else None for info in infos] + self._add_fields(index_fields, path + [str(i)]) + + def compare_two_fields(self, old: FieldInfo, new: FieldInfo, path: str) -> tuple[Resolution, str]: + if old is None and new is None: + return Resolution.NO, '' + if old is None: + if isinstance(new.value, dict): + value = '{}' + elif isinstance(new.value, list): + value = '[]' + else: + value = new.value + return Resolution.INFO, f'added <b>{value}</b>' + if new is None: + return Resolution.ERROR, 'deleted' + if old.id != new.id: + return Resolution.ERROR, f'id changed<br/>{old.id} -> {new.id}' + if type(old.value) is not type(new.value): + return Resolution.ERROR, 'type changed' + if isinstance(old.value, dict) or isinstance(old.value, list): + return Resolution.OK, '' + if old.value != new.value: + if path.startswith('FeatureFlags.') and isinstance(old.value, bool): + if not old.value: + return Resolution.INFO, 'FF switched on' + else: + return Resolution.ERROR, 'FF switched off' + return Resolution.WARNING, f'value changed<br/>{old.value} -> {new.value}' + return Resolution.OK, '' + + def compare(self): + for name, values in self.fields: + if len(values) == 0: + continue + result = [(Resolution.NO if values[0] is None else Resolution.OK, '')] + intresting = False + for i in range(1, len(values)): + result.append(self.compare_two_fields(values[i-1], values[i], name)) + intresting = intresting or result[-1][0] not in {Resolution.OK, Resolution.NO, Resolution.INFO} + if intresting: + self.resolutions.append((name, result)) + + def print_result(self) -> None: + print('<html><body><table border=1 valign="center">') + print('<thead style="position: sticky; top: 0; background: white; align: center"><tr><th style="padding-left: 10; padding-right: 10">field</th>') + for name in self.names: + print(f'<th style="padding-left: 10; padding-right: 10">{name}</th>') + print('</tr></thead>') + print('<tbody>') + for field, result in self.resolutions: + print(f'<tr><td style="padding-left: 10; padding-right: 10">{field}</td>') + for color, msg in result: + print(f'<td align="center" bgcolor="{color}" style="padding-left: 10; padding-right: 10">{msg}</td>') + print('</tr>') + print('</tbody>') + print('</table></body></html>') + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + differ = Differ() + differ.load_files(sys.argv[1:]) + differ.compare() + differ.print_result() diff --git a/ydb/tests/library/compatibility/configs/comparator/ya.make b/ydb/tests/library/compatibility/configs/comparator/ya.make new file mode 100644 index 00000000000..e05d90c2108 --- /dev/null +++ b/ydb/tests/library/compatibility/configs/comparator/ya.make @@ -0,0 +1,5 @@ +PY3_PROGRAM() + +PY_SRCS(__main__.py) + +END()
\ No newline at end of file diff --git a/ydb/tests/library/compatibility/configs/ya.make b/ydb/tests/library/compatibility/configs/ya.make index fb35e0383a3..20852af9316 100644 --- a/ydb/tests/library/compatibility/configs/ya.make +++ b/ydb/tests/library/compatibility/configs/ya.make @@ -1,34 +1,45 @@ RECURSE(dump) +RECURSE(comparator) -INCLUDE(${ARCADIA_ROOT}/ydb/tests/library/compatibility/versions.inc) +UNION() + +RUN_PROGRAM( + ydb/tests/library/compatibility/binaries/downloader download stable-25-1/release/config-meta.json stable-25-1 + OUT_NOAUTO stable-25-1 +) + +RUN_PROGRAM( + ydb/tests/library/compatibility/binaries/downloader download stable-25-1-1/release/config-meta.json stable-25-1-1 + OUT_NOAUTO stable-25-1-1 +) + +RUN_PROGRAM( + ydb/tests/library/compatibility/binaries/downloader download stable-25-1-2/release/config-meta.json stable-25-1-2 + OUT_NOAUTO stable-25-1-2 +) + +RUN_PROGRAM( + ydb/tests/library/compatibility/binaries/downloader download stable-25-1-3/release/config-meta.json stable-25-1-3 + OUT_NOAUTO stable-25-1-3 +) -# UNION() -# -# -# RUN_PROGRAM( -# ydb/tests/library/compatibility/binaries/downloader download $YDB_COMPAT_INTER_REF/release/config-meta.json inter $YDB_COMPAT_INTER_REF -# OUT_NOAUTO inter inter-name -# ) -# # RUN_PROGRAM( -# ydb/tests/library/compatibility/binaries/downloader download $YDB_COMPAT_INIT_REF/release/config-meta.json init $YDB_COMPAT_INIT_REF -# OUT_NOAUTO init init-name +# ydb/tests/library/compatibility/binaries/downloader download prestable-25-2/release/config-meta.json prestable-25-2 +# OUT_NOAUTO prestable-25-2 # ) -# -# IF(${YDB_COMPAT_TARGET_REF} != "current") -# RUN_PROGRAM( -# ydb/tests/library/compatibility/binaries/downloader download $YDB_COMPAT_TARGET_REF/release/config-meta.json target $YDB_COMPAT_TARGET_REF -# OUT_NOAUTO target target-name -# ) -# ELSE() -# RUN_PROGRAM( -# ydb/tests/library/compatibility/configs/dump/dumper -# STDOUT_NOAUTO target -# ) -# RUN( -# echo current -# STDOUT_NOAUTO target-name -# ) -# ENDIF() -# -# END() + +RUN_PROGRAM( + ydb/tests/library/compatibility/binaries/downloader download prestable-25-3/release/config-meta.json prestable-25-3 + OUT_NOAUTO prestable-25-3 +) + +RUN_PROGRAM( + ydb/tests/library/compatibility/configs/dump/dumper + STDOUT_NOAUTO current +) +RUN( + echo current + STDOUT_NOAUTO current-name +) + +END() |