Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/scripts/check-agent-gateway-migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Check Agent Gateway seed parity; not a native database syntax validator."""

import re
import sys
from pathlib import Path

DIALECTS = ("mysql", "ob", "og", "oracle", "pg")
EXPECTED = {
"plugin": {"68"},
"plugin_handle": {str(1960000000000001000 + i) for i in range(2)},
"resource": {str(1960000000000001010 + i) for i in range(10)},
"permission": {str(1960000000000001020 + i) for i in range(10)},
"namespace_plugin_rel": {"1960000000000001030"},
}
# The seed statements use SQL literals, including JSON strings and Oracle hints.
# Tokenize quoted text before stripping comments or normalizing whitespace.
TOKEN = re.compile(r"'(?:''|[^'])*'|/\*.*?\*/|--[^\n]*|[^\s]", re.S)
INSERT = re.compile(
r'INSERT\s+(?:/\*.*?\*/\s*)?INTO\s+(?:"public"\.)?'
r'[`"]?(\w+)[`"]?\s*(.*?)\bVALUES\s*\(\s*\'(\d+)\'', re.I | re.S
)


def seeds(sql):
"""Extract exactly the reserved seed keys, rejecting duplicates and omissions."""
result = {}
# Remove disabled statements without touching quoted values or Oracle hints.
sql = re.sub(r"'(?:''|[^'])*'|/\*.*?\*/|--[^\n]*",
lambda m: m.group() if m.group().startswith(("'", "/*+")) else " ", sql, flags=re.S)
for statement in re.finditer(
r"INSERT\s+(?:/\*.*?\*/\s*)?INTO\s+(?:'(?:''|[^'])*'|[^;'])*;", sql, re.I | re.S):
text = statement.group()
match = INSERT.match(text)
if not match:
continue
table, _, row_id = match.groups()
table = table.lower()
if table not in EXPECTED or row_id not in EXPECTED[table]:
continue
key = (table, row_id)
if key in result:
raise ValueError(f"duplicate seed {key}")
# Ignore formatting only; keep column lists, values, case and SQL hints.
result[key] = tuple(m.group() for m in TOKEN.finditer(text))
required = {(table, row_id) for table, ids in EXPECTED.items() for row_id in ids}
if result.keys() != required:
raise ValueError(f"missing seeds: {sorted(required - result.keys())}")
return result


def check_pair(schema, migration):
"""Compare every value and column list in each dialect's own seed records."""
expected, actual = seeds(schema), seeds(migration)
for key in expected:
if expected[key] != actual[key]:
raise ValueError(f"fresh-install/upgrade mismatch: {key}")
return len(expected)


def check(root):
"""Verify all supported upgrade dialects, regardless of working directory."""
for dialect in DIALECTS:
filename = "create-table.sql" if dialect in ("og", "pg") else "schema.sql"
schema = root / "db" / "init" / dialect / filename
migration = root / "db" / "upgrade" / f"2.7.1-upgrade-2.7.2-{dialect}.sql"
try:
count = check_pair(schema.read_text(encoding="utf-8"), migration.read_text(encoding="utf-8"))
except ValueError as error:
raise ValueError(f"{dialect}: {error}") from error
print(f"{dialect}: {count} Agent Gateway seed rows match")


if __name__ == "__main__":
try:
check(Path(__file__).resolve().parents[2])
except (OSError, ValueError) as error:
print(str(error), file=sys.stderr)
sys.exit(1)
76 changes: 76 additions & 0 deletions .github/scripts/test-agent-gateway-migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Regression tests for Agent Gateway migration/schema consistency checks."""

import importlib.util
from pathlib import Path
import sys
import unittest

sys.dont_write_bytecode = True

HERE = Path(__file__).resolve().parent
SPEC = importlib.util.spec_from_file_location("migration_check", HERE / "check-agent-gateway-migrations.py")
CHECK = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(CHECK)
ROOT = HERE.parents[1]


class MigrationConsistencyTest(unittest.TestCase):
def setUp(self):
self.schema = (ROOT / "db/init/mysql/schema.sql").read_text(encoding="utf-8")
self.migration = (ROOT / "db/upgrade/2.7.1-upgrade-2.7.2-mysql.sql").read_text(encoding="utf-8")

def test_all_dialects_match(self):
CHECK.check(ROOT)

def test_missing_each_required_record_is_detected(self):
for table, ids in CHECK.EXPECTED.items():
for row_id in ids:
with self.subTest(table=table, row_id=row_id):
changed = self.migration.replace(f"VALUES ('{row_id}'", "VALUES ('missing'", 1)
with self.assertRaisesRegex(ValueError, "missing seeds"):
CHECK.check_pair(self.schema, changed)

def test_duplicate_is_detected(self):
with self.assertRaisesRegex(ValueError, "duplicate seed"):
CHECK.check_pair(self.schema, self.migration + self.migration)

def test_changed_permission_is_detected(self):
changed = self.migration.replace("plugin:agentGatewayRule:delete", "plugin:agentGatewayRule:edit")
with self.assertRaisesRegex(ValueError, "mismatch"):
CHECK.check_pair(self.schema, changed)

def test_changed_default_is_detected(self):
changed = self.migration.replace('"defaultValue":"LLM"', '"defaultValue":"MCP"')
with self.assertRaisesRegex(ValueError, "mismatch"):
CHECK.check_pair(self.schema, changed)

def test_formatting_is_ignored(self):
self.assertEqual(24, CHECK.check_pair(self.schema, self.migration.replace("VALUES (", "VALUES\n (")))

def test_commented_out_migration_is_rejected(self):
disabled = "\n".join("-- " + line for line in self.migration.splitlines())
with self.assertRaisesRegex(ValueError, "missing seeds"):
CHECK.check_pair(self.schema, disabled)

def test_both_sides_missing_is_rejected(self):
with self.assertRaisesRegex(ValueError, "missing seeds"):
CHECK.check_pair("", "")


if __name__ == "__main__":
unittest.main()
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ concurrency:
cancel-in-progress: true

jobs:
agent_gateway_migrations:
if: github.repository == 'apache/shenyu'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check Agent Gateway upgrade and schema consistency
run: |
python3 .github/scripts/check-agent-gateway-migrations.py
python3 .github/scripts/test-agent-gateway-migrations.py

changes:
if: github.repository == 'apache/shenyu'
runs-on: ubuntu-latest
Expand Down
25 changes: 25 additions & 0 deletions db/init/mysql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,16 @@ INSERT INTO `permission` VALUES ('1697146861569542757', '1346358560427216896', '
INSERT INTO `permission` VALUES ('1697146861569542758', '1346358560427216896', '1844026199075534867', '2023-08-31 07:18:37', '2023-08-31 07:18:37');
INSERT INTO `permission` VALUES ('1697146861569542759', '1346358560427216896', '1844026199075534868', '2023-08-31 07:18:37', '2023-08-31 07:18:37');
INSERT INTO `permission` VALUES ('1697146861569542760', '1346358560427216896', '1844026199075534869', '2023-08-31 07:18:37', '2023-08-31 07:18:37');
INSERT INTO `permission` VALUES ('1960000000000001020', '1346358560427216896', '1960000000000001010', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001021', '1346358560427216896', '1960000000000001011', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001022', '1346358560427216896', '1960000000000001012', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001023', '1346358560427216896', '1960000000000001013', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001024', '1346358560427216896', '1960000000000001014', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001025', '1346358560427216896', '1960000000000001015', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001026', '1346358560427216896', '1960000000000001016', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001027', '1346358560427216896', '1960000000000001017', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001028', '1346358560427216896', '1960000000000001018', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `permission` VALUES ('1960000000000001029', '1346358560427216896', '1960000000000001019', '2026-09-19 00:00:00', '2026-09-19 00:00:00');

INSERT INTO `permission` VALUES ('1697146860569743000', '1346358560427216896', '1844026099075565000', '2023-08-31 06:59:01', '2023-08-31 06:59:01');
INSERT INTO `permission` VALUES ('1697146860569743001', '1346358560427216896', '1844026099075565001', '2023-08-31 07:22:07', '2023-08-31 07:22:07');
Expand Down Expand Up @@ -1019,6 +1029,7 @@ INSERT INTO `plugin` VALUES ('52', 'aiPrompt', null, 'Ai', 170, 0, '2023-12-20 1
INSERT INTO `plugin` VALUES ('53', 'aiRequestTransformer', NULL, 'Ai', 65, 0, '2023-12-20 18:02:53', '2023-12-20 18:02:53', null);

INSERT INTO `plugin` VALUES ('61', 'mcpServer', null, 'MCP', 180, 0, '2023-12-20 18:02:53', '2023-12-20 18:02:53', null);
INSERT INTO `plugin` VALUES ('68', 'agentGateway', null, 'Ai', 198, 0, '2026-09-19 00:00:00', '2026-09-19 00:00:00', null);
INSERT INTO `plugin` VALUES ('66', 'aiResponseTransformer', NULL, 'Ai', 66, 0, '2023-12-20 18:02:53', '2023-12-20 18:02:53', null);

-- ----------------------------
Expand Down Expand Up @@ -1473,6 +1484,8 @@ INSERT INTO `plugin_handle` VALUES ('1722804548510507248', '53', 'model', 'model
INSERT INTO `plugin_handle` VALUES ('1722804548510507249', '53', 'content', 'content', 2, 2, 4, '{"required":"0","rule":""}', '2025-03-12 06:02:32.450', '2025-03-12 06:02:32.450');

INSERT INTO `plugin_handle` VALUES ('1942847622591684608', '61', 'messageEndpoint', 'messageEndpoint', 2, 1, 0, '{"required":"0","defaultValue":"/message","rule":""}', '2025-07-09 07:25:44.249', '2025-07-09 07:25:44.249');
INSERT INTO `plugin_handle` VALUES ('1960000000000001000', '68', 'trafficType', 'trafficType', 2, 2, 0, '{"required":"1","defaultValue":"LLM","rule":""}', '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `plugin_handle` VALUES ('1960000000000001001', '68', 'responseRequestId', 'responseRequestId', 3, 2, 1, '{"required":"0","defaultValue":"false","rule":""}', '2026-09-19 00:00:00', '2026-09-19 00:00:00');

INSERT INTO `plugin_handle` VALUES ('1722804548510507350', '66', 'provider', 'provider', 3, 3, 0, '{"required":"0","rule":""}', '2025-03-12 06:01:49.725', '2025-03-12 06:07:49.856');
INSERT INTO `plugin_handle` VALUES ('1722804548510507351', '66', 'baseUrl', 'baseUrl', 2, 3, 1, '{"required":"0","rule":""}', '2025-03-12 06:02:04.155', '2025-03-12 06:02:04.155');
Expand Down Expand Up @@ -2071,6 +2084,17 @@ INSERT INTO `resource` VALUES ('1844026199075534867', '1844026199075534860', 'SH
INSERT INTO `resource` VALUES ('1844026199075534868', '1844026199075534860', 'SHENYU.BUTTON.PLUGIN.RULE.DELETE', '', '', '', 2, 0, '', 1, 0, 'plugin:mcpServerRule:delete', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');
INSERT INTO `resource` VALUES ('1844026199075534869', '1844026199075534860', 'SHENYU.BUTTON.PLUGIN.SYNCHRONIZE', '', '', '', 2, 0, '', 1, 0, 'plugin:mcpServer:modify', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');

INSERT INTO `resource` VALUES ('1960000000000001010', '1346775491550474240', 'agentGateway', 'agentGateway', '/plug/agentGateway', 'agentGateway', 1, 0, 'pic-center', 0, 0, '', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001011', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.SELECTOR.ADD', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewaySelector:add', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001012', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.SELECTOR.QUERY', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewaySelector:query', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001013', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.SELECTOR.EDIT', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewaySelector:edit', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001014', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.SELECTOR.DELETE', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewaySelector:delete', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001015', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.RULE.ADD', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewayRule:add', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001016', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.RULE.QUERY', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewayRule:query', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001017', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.RULE.EDIT', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewayRule:edit', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001018', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.RULE.DELETE', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGatewayRule:delete', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');
INSERT INTO `resource` VALUES ('1960000000000001019', '1960000000000001010', 'SHENYU.BUTTON.PLUGIN.SYNCHRONIZE', '', '', '', 2, 0, '', 1, 0, 'plugin:agentGateway:modify', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');

INSERT INTO `resource` VALUES ('1844026099075565000', '1346775491550474240', 'aiResponseTransformer', 'aiResponseTransformer', '/plug/aiResponseTransformer', 'aiResponseTransformer', 1, 0, 'pic-center', 0, 0, '', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');
INSERT INTO `resource` VALUES ('1844026099075565001', '1844026099075565000', 'SHENYU.BUTTON.PLUGIN.SELECTOR.ADD', '', '', '', 2, 0, '', 1, 0, 'plugin:aiResponseTransformerSelector:add', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');
INSERT INTO `resource` VALUES ('1844026099075565002', '1844026099075565000', 'SHENYU.BUTTON.PLUGIN.SELECTOR.QUERY', '', '', '', 2, 0, '', 1, 0, 'plugin:aiResponseTransformerSelector:query', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');
Expand Down Expand Up @@ -2645,6 +2669,7 @@ INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, `
INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES ('1801816010882822190','649330b6-c2d7-4edc-be8e-8a54df9eb385','53', NULL, 65, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');

INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES ('1801816010882832189','649330b6-c2d7-4edc-be8e-8a54df9eb385','61', NULL, 180, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES ('1960000000000001030','649330b6-c2d7-4edc-be8e-8a54df9eb385','68', NULL, 198, 0, '2026-09-19 00:00:00.000', '2026-09-19 00:00:00.000');

INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES ('1801816010882822300','649330b6-c2d7-4edc-be8e-8a54df9eb385','66', NULL, 66, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');

Expand Down
Loading
Loading