feat: 增加网络端口转发组与通道模型
This commit is contained in:
parent
9cea77ff49
commit
e51168d669
@ -2,39 +2,72 @@
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `network_port_forward_group` (
|
||||
`id` BIGINT NOT NULL,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`remark` TEXT NULL,
|
||||
`external_port` INT UNSIGNED NOT NULL,
|
||||
`internal_port` INT UNSIGNED NOT NULL,
|
||||
`protocol_mode` VARCHAR(8) NOT NULL,
|
||||
`target_ipv4` VARCHAR(15) NOT NULL,
|
||||
`is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`create_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`update_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `network_port_forward` (
|
||||
`id` BIGINT NOT NULL,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`remark` TEXT NULL,
|
||||
`group_id` BIGINT NOT NULL,
|
||||
`protocol` VARCHAR(8) NOT NULL,
|
||||
`external_port` INT UNSIGNED NOT NULL,
|
||||
`internal_port` INT UNSIGNED NOT NULL,
|
||||
`active_key` VARCHAR(32) NULL,
|
||||
`active_group_protocol_key` VARCHAR(64) NULL,
|
||||
`target_ipv4` VARCHAR(15) NOT NULL,
|
||||
`desired_presence` VARCHAR(16) NOT NULL DEFAULT 'present',
|
||||
`keeper_desired_enabled` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`natmap_desired_enabled` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`probe_request_id` VARCHAR(64) NULL,
|
||||
`desired_revision` BIGINT NOT NULL DEFAULT 0,
|
||||
`desired_issued_at` DATETIME(6) NOT NULL,
|
||||
`reported_revision` BIGINT NOT NULL DEFAULT 0,
|
||||
`sync_status` VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||
`keeper_status` VARCHAR(16) NOT NULL DEFAULT 'disabled',
|
||||
`natmap_status` VARCHAR(16) NOT NULL DEFAULT 'disabled',
|
||||
`current_public_ipv4` VARCHAR(15) NULL,
|
||||
`current_public_port` INT NULL,
|
||||
`current_observed_at` DATETIME(6) NULL,
|
||||
`current_validated_at` DATETIME(6) NULL,
|
||||
`current_valid_until` DATETIME(6) NULL,
|
||||
`last_observed_ipv4` VARCHAR(15) NULL,
|
||||
`last_observed_port` INT NULL,
|
||||
`last_observed_at` DATETIME(6) NULL,
|
||||
`last_observed_validated_at` DATETIME(6) NULL,
|
||||
`candidate_public_ipv4` VARCHAR(15) NULL,
|
||||
`candidate_public_port` INT NULL,
|
||||
`candidate_observed_at` DATETIME(6) NULL,
|
||||
`candidate_validated_at` DATETIME(6) NULL,
|
||||
`last_published_public_ipv4` VARCHAR(15) NULL,
|
||||
`last_published_public_port` INT NULL,
|
||||
`last_published_at` DATETIME(6) NULL,
|
||||
`last_error_code` VARCHAR(64) NULL,
|
||||
`last_error_message` VARCHAR(512) NULL,
|
||||
`keeper_last_error_code` VARCHAR(64) NULL,
|
||||
`keeper_last_error_message` VARCHAR(512) NULL,
|
||||
`natmap_last_error_code` VARCHAR(64) NULL,
|
||||
`natmap_last_error_message` VARCHAR(512) NULL,
|
||||
`is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`create_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`update_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_network_port_forward_active_key` (`active_key`),
|
||||
UNIQUE KEY `uk_network_port_forward_active_group_protocol_key` (`active_group_protocol_key`),
|
||||
KEY `idx_network_port_forward_status` (`is_deleted`, `sync_status`),
|
||||
KEY `idx_network_port_forward_protocol` (`protocol`, `external_port`)
|
||||
KEY `idx_network_port_forward_protocol` (`protocol`, `external_port`),
|
||||
KEY `idx_network_port_forward_group` (`group_id`, `is_deleted`, `protocol`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `network_ddns_record` (
|
||||
@ -73,6 +106,10 @@ CREATE TABLE IF NOT EXISTS `network_agent_state` (
|
||||
`desired_revision` BIGINT NOT NULL DEFAULT 0,
|
||||
`desired_issued_at` DATETIME(6) NOT NULL,
|
||||
`published_revision` BIGINT NOT NULL DEFAULT 0,
|
||||
`desired_schema_version` INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`published_schema_version` INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`max_supported_schema_version` INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`tcp_natmap_capable` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`applied_revision` BIGINT NOT NULL DEFAULT 0,
|
||||
`online` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`version` VARCHAR(64) NULL,
|
||||
@ -126,6 +163,7 @@ CREATE TABLE IF NOT EXISTS `network_endpoint_history` (
|
||||
`event_id` VARCHAR(128) NOT NULL,
|
||||
`mapping_id` BIGINT NOT NULL,
|
||||
`event_type` VARCHAR(16) NOT NULL,
|
||||
`mechanism` VARCHAR(16) NOT NULL DEFAULT 'udp_stun',
|
||||
`public_ipv4` VARCHAR(15) NULL,
|
||||
`public_port` INT NULL,
|
||||
`first_observed_at` DATETIME(6) NOT NULL,
|
||||
|
||||
43
sql/network-tcp-natmap-v2-verify.sql
Normal file
43
sql/network-tcp-natmap-v2-verify.sql
Normal file
@ -0,0 +1,43 @@
|
||||
-- Read-only post-migration verification for TCP NATMap v2.
|
||||
|
||||
SELECT 'network_port_forward_group' AS table_name, COUNT(*) AS row_count
|
||||
FROM `network_port_forward_group`;
|
||||
|
||||
SELECT 'network_port_forward_group_id_null_count' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM `network_port_forward`
|
||||
WHERE `group_id` IS NULL;
|
||||
|
||||
SELECT 'network_port_forward_active_group_protocol_key_conflicts' AS check_name,
|
||||
COUNT(*) AS matched_rows
|
||||
FROM (
|
||||
SELECT `active_group_protocol_key`
|
||||
FROM `network_port_forward`
|
||||
WHERE `active_group_protocol_key` IS NOT NULL
|
||||
GROUP BY `active_group_protocol_key`
|
||||
HAVING COUNT(*) > 1
|
||||
) conflicts;
|
||||
|
||||
SELECT 'network_port_forward_group_id' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'group_id'
|
||||
AND COLUMN_TYPE = 'bigint'
|
||||
AND IS_NULLABLE = 'NO';
|
||||
|
||||
SELECT 'network_port_forward_v2_indexes' AS check_name, INDEX_NAME, NON_UNIQUE
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'network_port_forward'
|
||||
AND INDEX_NAME IN (
|
||||
'uk_network_port_forward_active_group_protocol_key',
|
||||
'idx_network_port_forward_group'
|
||||
);
|
||||
|
||||
SELECT 'network_endpoint_history_mechanism' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'network_endpoint_history'
|
||||
AND COLUMN_NAME = 'mechanism'
|
||||
AND COLUMN_TYPE = 'varchar(16)'
|
||||
AND IS_NULLABLE = 'NO';
|
||||
247
sql/network-tcp-natmap-v2.sql
Normal file
247
sql/network-tcp-natmap-v2.sql
Normal file
@ -0,0 +1,247 @@
|
||||
-- Additive TCP NATMap v2 group/channel migration. Safe to rerun.
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `network_port_forward_group` (
|
||||
`id` BIGINT NOT NULL,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`remark` TEXT NULL,
|
||||
`external_port` INT UNSIGNED NOT NULL,
|
||||
`internal_port` INT UNSIGNED NOT NULL,
|
||||
`protocol_mode` VARCHAR(8) NOT NULL,
|
||||
`target_ipv4` VARCHAR(15) NOT NULL,
|
||||
`is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`create_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`update_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
SET @network_port_forward_group_id_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'group_id'
|
||||
);
|
||||
SET @network_port_forward_group_id_sql := IF(
|
||||
@network_port_forward_group_id_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD COLUMN `group_id` BIGINT NULL AFTER `remark`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_group_id_stmt FROM @network_port_forward_group_id_sql;
|
||||
EXECUTE network_port_forward_group_id_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_group_id_stmt;
|
||||
|
||||
SET @network_port_forward_active_group_protocol_key_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'active_group_protocol_key'
|
||||
);
|
||||
SET @network_port_forward_active_group_protocol_key_sql := IF(
|
||||
@network_port_forward_active_group_protocol_key_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD COLUMN `active_group_protocol_key` VARCHAR(64) NULL AFTER `active_key`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_active_group_protocol_key_stmt FROM @network_port_forward_active_group_protocol_key_sql;
|
||||
EXECUTE network_port_forward_active_group_protocol_key_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_active_group_protocol_key_stmt;
|
||||
|
||||
SET @network_port_forward_natmap_desired_enabled_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'natmap_desired_enabled'
|
||||
);
|
||||
SET @network_port_forward_natmap_desired_enabled_sql := IF(
|
||||
@network_port_forward_natmap_desired_enabled_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD COLUMN `natmap_desired_enabled` TINYINT(1) NOT NULL DEFAULT 0 AFTER `keeper_desired_enabled`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_natmap_desired_enabled_stmt FROM @network_port_forward_natmap_desired_enabled_sql;
|
||||
EXECUTE network_port_forward_natmap_desired_enabled_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_natmap_desired_enabled_stmt;
|
||||
|
||||
SET @network_port_forward_natmap_status_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'natmap_status'
|
||||
);
|
||||
SET @network_port_forward_natmap_status_sql := IF(
|
||||
@network_port_forward_natmap_status_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD COLUMN `natmap_status` VARCHAR(16) NOT NULL DEFAULT ''disabled'' AFTER `keeper_status`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_natmap_status_stmt FROM @network_port_forward_natmap_status_sql;
|
||||
EXECUTE network_port_forward_natmap_status_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_natmap_status_stmt;
|
||||
|
||||
SET @network_port_forward_runtime_columns_sql := (
|
||||
SELECT GROUP_CONCAT(CONCAT(
|
||||
'ADD COLUMN `', column_name, '` ', column_definition, ' ', column_position
|
||||
) SEPARATOR ', ')
|
||||
FROM (
|
||||
SELECT 'natmap_last_error_code' AS column_name, 'VARCHAR(64) NULL' AS column_definition, 'AFTER `last_error_message`' AS column_position UNION ALL
|
||||
SELECT 'natmap_last_error_message', 'VARCHAR(512) NULL', 'AFTER `last_error_message`' UNION ALL
|
||||
SELECT 'keeper_last_error_code', 'VARCHAR(64) NULL', 'AFTER `last_error_message`' UNION ALL
|
||||
SELECT 'keeper_last_error_message', 'VARCHAR(512) NULL', 'AFTER `last_error_message`' UNION ALL
|
||||
SELECT 'candidate_public_ipv4', 'VARCHAR(15) NULL', 'AFTER `current_public_port`' UNION ALL
|
||||
SELECT 'candidate_public_port', 'INT NULL', 'AFTER `current_public_port`' UNION ALL
|
||||
SELECT 'candidate_observed_at', 'DATETIME(6) NULL', 'AFTER `current_public_port`' UNION ALL
|
||||
SELECT 'candidate_validated_at', 'DATETIME(6) NULL', 'AFTER `current_public_port`' UNION ALL
|
||||
SELECT 'current_validated_at', 'DATETIME(6) NULL', 'AFTER `current_observed_at`' UNION ALL
|
||||
SELECT 'last_observed_validated_at', 'DATETIME(6) NULL', 'AFTER `last_observed_at`' UNION ALL
|
||||
SELECT 'last_published_public_ipv4', 'VARCHAR(15) NULL', 'AFTER `last_observed_at`' UNION ALL
|
||||
SELECT 'last_published_public_port', 'INT NULL', 'AFTER `last_observed_at`' UNION ALL
|
||||
SELECT 'last_published_at', 'DATETIME(6) NULL', 'AFTER `last_observed_at`'
|
||||
) expected
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS actual
|
||||
WHERE actual.TABLE_SCHEMA = DATABASE()
|
||||
AND actual.TABLE_NAME = 'network_port_forward'
|
||||
AND actual.COLUMN_NAME = expected.column_name
|
||||
)
|
||||
);
|
||||
SET @network_port_forward_runtime_columns_sql := IF(
|
||||
@network_port_forward_runtime_columns_sql IS NULL,
|
||||
'SELECT 1',
|
||||
CONCAT('ALTER TABLE `network_port_forward` ', @network_port_forward_runtime_columns_sql)
|
||||
);
|
||||
PREPARE network_port_forward_runtime_columns_stmt FROM @network_port_forward_runtime_columns_sql;
|
||||
EXECUTE network_port_forward_runtime_columns_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_runtime_columns_stmt;
|
||||
|
||||
SET @network_agent_state_v2_columns_sql := (
|
||||
SELECT GROUP_CONCAT(CONCAT(
|
||||
'ADD COLUMN `', column_name, '` ', column_definition, ' ', column_position
|
||||
) SEPARATOR ', ')
|
||||
FROM (
|
||||
SELECT 'desired_schema_version' AS column_name, 'INT UNSIGNED NOT NULL DEFAULT 1' AS column_definition, 'AFTER `desired_revision`' AS column_position UNION ALL
|
||||
SELECT 'published_schema_version', 'INT UNSIGNED NOT NULL DEFAULT 1', 'AFTER `published_revision`' UNION ALL
|
||||
SELECT 'max_supported_schema_version', 'INT UNSIGNED NOT NULL DEFAULT 1', 'AFTER `published_revision`' UNION ALL
|
||||
SELECT 'tcp_natmap_capable', 'TINYINT(1) NOT NULL DEFAULT 0', 'AFTER `published_revision`'
|
||||
) expected
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS actual
|
||||
WHERE actual.TABLE_SCHEMA = DATABASE()
|
||||
AND actual.TABLE_NAME = 'network_agent_state'
|
||||
AND actual.COLUMN_NAME = expected.column_name
|
||||
)
|
||||
);
|
||||
SET @network_agent_state_v2_columns_sql := IF(
|
||||
@network_agent_state_v2_columns_sql IS NULL,
|
||||
'SELECT 1',
|
||||
CONCAT('ALTER TABLE `network_agent_state` ', @network_agent_state_v2_columns_sql)
|
||||
);
|
||||
PREPARE network_agent_state_v2_columns_stmt FROM @network_agent_state_v2_columns_sql;
|
||||
EXECUTE network_agent_state_v2_columns_stmt;
|
||||
DEALLOCATE PREPARE network_agent_state_v2_columns_stmt;
|
||||
|
||||
SET @network_endpoint_history_mechanism_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_endpoint_history'
|
||||
AND COLUMN_NAME = 'mechanism'
|
||||
);
|
||||
SET @network_endpoint_history_mechanism_sql := IF(
|
||||
@network_endpoint_history_mechanism_exists = 0,
|
||||
'ALTER TABLE `network_endpoint_history` ADD COLUMN `mechanism` VARCHAR(16) NULL AFTER `event_type`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_endpoint_history_mechanism_stmt FROM @network_endpoint_history_mechanism_sql;
|
||||
EXECUTE network_endpoint_history_mechanism_stmt;
|
||||
DEALLOCATE PREPARE network_endpoint_history_mechanism_stmt;
|
||||
|
||||
INSERT INTO `network_port_forward_group` (
|
||||
`id`, `name`, `remark`, `external_port`, `internal_port`, `protocol_mode`,
|
||||
`target_ipv4`, `is_deleted`, `create_time`, `update_time`
|
||||
)
|
||||
SELECT channel.id, channel.name, channel.remark, channel.external_port,
|
||||
channel.internal_port, 'udp', channel.target_ipv4, channel.is_deleted,
|
||||
channel.create_time, channel.update_time
|
||||
FROM `network_port_forward` channel
|
||||
LEFT JOIN `network_port_forward_group` grouped ON grouped.id = channel.id
|
||||
WHERE grouped.id IS NULL;
|
||||
|
||||
UPDATE `network_port_forward` channel
|
||||
SET `group_id` = channel.id
|
||||
WHERE channel.group_id IS NULL;
|
||||
|
||||
UPDATE `network_port_forward` channel
|
||||
SET `active_group_protocol_key` = CASE
|
||||
WHEN channel.is_deleted = 0 THEN CONCAT(channel.group_id, ':', channel.protocol)
|
||||
ELSE NULL
|
||||
END
|
||||
WHERE channel.active_group_protocol_key IS NULL
|
||||
OR channel.is_deleted <> 0;
|
||||
|
||||
UPDATE `network_endpoint_history`
|
||||
SET `mechanism` = 'udp_stun'
|
||||
WHERE `mechanism` IS NULL;
|
||||
|
||||
SET @network_endpoint_history_mechanism_nullable := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_endpoint_history'
|
||||
AND COLUMN_NAME = 'mechanism' AND IS_NULLABLE = 'YES'
|
||||
);
|
||||
SET @network_endpoint_history_mechanism_sql := IF(
|
||||
@network_endpoint_history_mechanism_nullable = 1,
|
||||
'ALTER TABLE `network_endpoint_history` MODIFY COLUMN `mechanism` VARCHAR(16) NOT NULL DEFAULT ''udp_stun''',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_endpoint_history_mechanism_not_null_stmt FROM @network_endpoint_history_mechanism_sql;
|
||||
EXECUTE network_endpoint_history_mechanism_not_null_stmt;
|
||||
DEALLOCATE PREPARE network_endpoint_history_mechanism_not_null_stmt;
|
||||
|
||||
SET @network_port_forward_group_id_null_count := (
|
||||
SELECT COUNT(*) FROM `network_port_forward` WHERE `group_id` IS NULL
|
||||
);
|
||||
SELECT @network_port_forward_group_id_null_count AS network_port_forward_group_id_null_count;
|
||||
SET @network_port_forward_group_id_nullable := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND COLUMN_NAME = 'group_id' AND IS_NULLABLE = 'YES'
|
||||
);
|
||||
SET @network_port_forward_group_id_not_null_sql := IF(
|
||||
@network_port_forward_group_id_null_count = 0
|
||||
AND @network_port_forward_group_id_nullable = 1,
|
||||
'ALTER TABLE `network_port_forward` MODIFY COLUMN `group_id` BIGINT NOT NULL',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_group_id_not_null_stmt FROM @network_port_forward_group_id_not_null_sql;
|
||||
EXECUTE network_port_forward_group_id_not_null_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_group_id_not_null_stmt;
|
||||
|
||||
SET @network_port_forward_active_group_protocol_key_conflicts := (
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT `active_group_protocol_key`
|
||||
FROM `network_port_forward`
|
||||
WHERE `active_group_protocol_key` IS NOT NULL
|
||||
GROUP BY `active_group_protocol_key`
|
||||
HAVING COUNT(*) > 1
|
||||
) conflicts
|
||||
);
|
||||
SELECT @network_port_forward_active_group_protocol_key_conflicts AS network_port_forward_active_group_protocol_key_conflicts;
|
||||
SET @network_port_forward_active_group_protocol_key_index_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND INDEX_NAME = 'uk_network_port_forward_active_group_protocol_key'
|
||||
);
|
||||
SET @network_port_forward_active_group_protocol_key_index_sql := IF(
|
||||
@network_port_forward_active_group_protocol_key_conflicts = 0
|
||||
AND @network_port_forward_active_group_protocol_key_index_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD UNIQUE KEY `uk_network_port_forward_active_group_protocol_key` (`active_group_protocol_key`)',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_active_group_protocol_key_index_stmt FROM @network_port_forward_active_group_protocol_key_index_sql;
|
||||
EXECUTE network_port_forward_active_group_protocol_key_index_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_active_group_protocol_key_index_stmt;
|
||||
|
||||
SET @network_port_forward_group_index_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'network_port_forward'
|
||||
AND INDEX_NAME = 'idx_network_port_forward_group'
|
||||
);
|
||||
SET @network_port_forward_group_index_sql := IF(
|
||||
@network_port_forward_group_index_exists = 0,
|
||||
'ALTER TABLE `network_port_forward` ADD KEY `idx_network_port_forward_group` (`group_id`, `is_deleted`, `protocol`)',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE network_port_forward_group_index_stmt FROM @network_port_forward_group_index_sql;
|
||||
EXECUTE network_port_forward_group_index_stmt;
|
||||
DEALLOCATE PREPARE network_port_forward_group_index_stmt;
|
||||
@ -126,38 +126,70 @@ CREATE TABLE IF NOT EXISTS admin_notice (
|
||||
KEY idx_admin_notice_last_seen (last_seen_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_port_forward_group (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
remark TEXT NULL,
|
||||
external_port INT UNSIGNED NOT NULL,
|
||||
internal_port INT UNSIGNED NOT NULL,
|
||||
protocol_mode VARCHAR(8) NOT NULL,
|
||||
target_ipv4 VARCHAR(15) NOT NULL,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_port_forward (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
remark TEXT NULL,
|
||||
group_id BIGINT NOT NULL,
|
||||
protocol VARCHAR(8) NOT NULL,
|
||||
external_port INT UNSIGNED NOT NULL,
|
||||
internal_port INT UNSIGNED NOT NULL,
|
||||
active_key VARCHAR(32) NULL,
|
||||
active_group_protocol_key VARCHAR(64) NULL,
|
||||
target_ipv4 VARCHAR(15) NOT NULL,
|
||||
desired_presence VARCHAR(16) NOT NULL DEFAULT 'present',
|
||||
keeper_desired_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||
natmap_desired_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||
probe_request_id VARCHAR(64) NULL,
|
||||
desired_revision BIGINT NOT NULL DEFAULT 0,
|
||||
desired_issued_at DATETIME(6) NOT NULL,
|
||||
reported_revision BIGINT NOT NULL DEFAULT 0,
|
||||
sync_status VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||
keeper_status VARCHAR(16) NOT NULL DEFAULT 'disabled',
|
||||
natmap_status VARCHAR(16) NOT NULL DEFAULT 'disabled',
|
||||
current_public_ipv4 VARCHAR(15) NULL,
|
||||
current_public_port INT NULL,
|
||||
current_observed_at DATETIME(6) NULL,
|
||||
current_validated_at DATETIME(6) NULL,
|
||||
current_valid_until DATETIME(6) NULL,
|
||||
last_observed_ipv4 VARCHAR(15) NULL,
|
||||
last_observed_port INT NULL,
|
||||
last_observed_at DATETIME(6) NULL,
|
||||
last_observed_validated_at DATETIME(6) NULL,
|
||||
candidate_public_ipv4 VARCHAR(15) NULL,
|
||||
candidate_public_port INT NULL,
|
||||
candidate_observed_at DATETIME(6) NULL,
|
||||
candidate_validated_at DATETIME(6) NULL,
|
||||
last_published_public_ipv4 VARCHAR(15) NULL,
|
||||
last_published_public_port INT NULL,
|
||||
last_published_at DATETIME(6) NULL,
|
||||
last_error_code VARCHAR(64) NULL,
|
||||
last_error_message VARCHAR(512) NULL,
|
||||
keeper_last_error_code VARCHAR(64) NULL,
|
||||
keeper_last_error_message VARCHAR(512) NULL,
|
||||
natmap_last_error_code VARCHAR(64) NULL,
|
||||
natmap_last_error_message VARCHAR(512) NULL,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_network_port_forward_active_key (active_key),
|
||||
UNIQUE KEY uk_network_port_forward_active_group_protocol_key (active_group_protocol_key),
|
||||
KEY idx_network_port_forward_status (is_deleted, sync_status),
|
||||
KEY idx_network_port_forward_protocol (protocol, external_port)
|
||||
KEY idx_network_port_forward_protocol (protocol, external_port),
|
||||
KEY idx_network_port_forward_group (group_id, is_deleted, protocol)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_ddns_record (
|
||||
@ -195,6 +227,10 @@ CREATE TABLE IF NOT EXISTS network_agent_state (
|
||||
desired_revision BIGINT NOT NULL DEFAULT 0,
|
||||
desired_issued_at DATETIME(6) NOT NULL,
|
||||
published_revision BIGINT NOT NULL DEFAULT 0,
|
||||
desired_schema_version INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
published_schema_version INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
max_supported_schema_version INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
tcp_natmap_capable TINYINT(1) NOT NULL DEFAULT 0,
|
||||
applied_revision BIGINT NOT NULL DEFAULT 0,
|
||||
online TINYINT(1) NOT NULL DEFAULT 0,
|
||||
version VARCHAR(64) NULL,
|
||||
@ -215,6 +251,7 @@ CREATE TABLE IF NOT EXISTS network_endpoint_history (
|
||||
event_id VARCHAR(128) NOT NULL,
|
||||
mapping_id BIGINT NOT NULL,
|
||||
event_type VARCHAR(16) NOT NULL,
|
||||
mechanism VARCHAR(16) NOT NULL DEFAULT 'udp_stun',
|
||||
public_ipv4 VARCHAR(15) NULL,
|
||||
public_port INT NULL,
|
||||
first_observed_at DATETIME(6) NOT NULL,
|
||||
|
||||
@ -17,6 +17,7 @@ import { NetworkEndpointHistory } from '@/modules/admin/platform-config/network-
|
||||
import { NetworkManagementController } from '@/modules/admin/platform-config/network-management/network-management.controller';
|
||||
import { NetworkManagementEventStreamService } from '@/modules/admin/platform-config/network-management/network-management-event-stream.service';
|
||||
import { NetworkPortForward } from '@/modules/admin/platform-config/network-management/network-management.entity';
|
||||
import { NetworkPortForwardGroup } from '@/modules/admin/platform-config/network-management/network-port-forward-group.entity';
|
||||
import { NetworkManagementService } from '@/modules/admin/platform-config/network-management/network-management.service';
|
||||
import { NetworkStunMessageSourceAdapter } from '@/modules/admin/platform-config/network-management/network-stun-message-source.adapter';
|
||||
import { SystemLogController } from '@/modules/admin/platform-config/system-log/system-log.controller';
|
||||
@ -99,6 +100,7 @@ export const ADMIN_PLATFORM_CONFIG_PROVIDERS = [
|
||||
Component,
|
||||
AdminUser,
|
||||
NetworkPortForward,
|
||||
NetworkPortForwardGroup,
|
||||
NetworkAgentState,
|
||||
NetworkEndpointHistory,
|
||||
NetworkDdnsRecord,
|
||||
|
||||
@ -23,6 +23,33 @@ export class NetworkAgentState {
|
||||
@Column({ default: '0', name: 'published_revision', type: 'bigint' })
|
||||
publishedRevision: string;
|
||||
|
||||
@Column({
|
||||
default: 1,
|
||||
name: 'desired_schema_version',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
})
|
||||
desiredSchemaVersion: number;
|
||||
|
||||
@Column({
|
||||
default: 1,
|
||||
name: 'published_schema_version',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
})
|
||||
publishedSchemaVersion: number;
|
||||
|
||||
@Column({
|
||||
default: 1,
|
||||
name: 'max_supported_schema_version',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
})
|
||||
maxSupportedSchemaVersion: number;
|
||||
|
||||
@Column({ default: false, name: 'tcp_natmap_capable', type: 'boolean' })
|
||||
tcpNatmapCapable: boolean;
|
||||
|
||||
@Column({ default: '0', name: 'applied_revision', type: 'bigint' })
|
||||
appliedRevision: string;
|
||||
|
||||
|
||||
@ -23,6 +23,9 @@ export class NetworkEndpointHistory {
|
||||
@Column({ length: 16, name: 'event_type' })
|
||||
eventType: EndpointEventType;
|
||||
|
||||
@Column({ default: 'udp_stun', length: 16, name: 'mechanism' })
|
||||
mechanism: string;
|
||||
|
||||
@Column({ length: 15, name: 'public_ipv4', nullable: true })
|
||||
publicIpv4?: string | null;
|
||||
|
||||
|
||||
@ -15,6 +15,10 @@ import type {
|
||||
|
||||
@Entity('network_port_forward')
|
||||
@Index('uk_network_port_forward_active_key', ['activeKey'], { unique: true })
|
||||
@Index('uk_network_port_forward_active_group_protocol_key', [
|
||||
'activeGroupProtocolKey',
|
||||
], { unique: true })
|
||||
@Index('idx_network_port_forward_group', ['groupId', 'isDeleted', 'protocol'])
|
||||
export class NetworkPortForward {
|
||||
@PrimaryColumn({ type: 'bigint' })
|
||||
id: string;
|
||||
@ -25,6 +29,9 @@ export class NetworkPortForward {
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
remark?: string | null;
|
||||
|
||||
@Column({ name: 'group_id', type: 'bigint' })
|
||||
groupId: string;
|
||||
|
||||
@Column({ length: 8 })
|
||||
protocol: PortForwardProtocol;
|
||||
|
||||
@ -37,6 +44,13 @@ export class NetworkPortForward {
|
||||
@Column({ length: 32, name: 'active_key', nullable: true })
|
||||
activeKey?: string | null;
|
||||
|
||||
@Column({
|
||||
length: 64,
|
||||
name: 'active_group_protocol_key',
|
||||
nullable: true,
|
||||
})
|
||||
activeGroupProtocolKey?: string | null;
|
||||
|
||||
@Column({ length: 15, name: 'target_ipv4' })
|
||||
targetIpv4: string;
|
||||
|
||||
@ -50,6 +64,13 @@ export class NetworkPortForward {
|
||||
})
|
||||
keeperDesiredEnabled: boolean;
|
||||
|
||||
@Column({
|
||||
default: false,
|
||||
name: 'natmap_desired_enabled',
|
||||
type: 'boolean',
|
||||
})
|
||||
natmapDesiredEnabled: boolean;
|
||||
|
||||
@Column({ length: 64, name: 'probe_request_id', nullable: true })
|
||||
probeRequestId?: string | null;
|
||||
|
||||
@ -68,6 +89,9 @@ export class NetworkPortForward {
|
||||
@Column({ default: 'disabled', length: 16, name: 'keeper_status' })
|
||||
keeperStatus: KeeperStatus;
|
||||
|
||||
@Column({ default: 'disabled', length: 16, name: 'natmap_status' })
|
||||
natmapStatus: string;
|
||||
|
||||
@Column({ length: 15, name: 'current_public_ipv4', nullable: true })
|
||||
currentPublicIpv4?: string | null;
|
||||
|
||||
@ -81,6 +105,14 @@ export class NetworkPortForward {
|
||||
})
|
||||
currentObservedAt?: KtDateTime | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'current_validated_at',
|
||||
nullable: true,
|
||||
precision: 6,
|
||||
type: 'datetime',
|
||||
})
|
||||
currentValidatedAt?: KtDateTime | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'current_valid_until',
|
||||
nullable: true,
|
||||
@ -101,12 +133,68 @@ export class NetworkPortForward {
|
||||
})
|
||||
lastObservedAt?: KtDateTime | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'last_observed_validated_at',
|
||||
nullable: true,
|
||||
precision: 6,
|
||||
type: 'datetime',
|
||||
})
|
||||
lastObservedValidatedAt?: KtDateTime | null;
|
||||
|
||||
@Column({ length: 15, name: 'candidate_public_ipv4', nullable: true })
|
||||
candidatePublicIpv4?: string | null;
|
||||
|
||||
@Column({ name: 'candidate_public_port', nullable: true, type: 'int' })
|
||||
candidatePublicPort?: number | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'candidate_observed_at',
|
||||
nullable: true,
|
||||
precision: 6,
|
||||
type: 'datetime',
|
||||
})
|
||||
candidateObservedAt?: KtDateTime | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'candidate_validated_at',
|
||||
nullable: true,
|
||||
precision: 6,
|
||||
type: 'datetime',
|
||||
})
|
||||
candidateValidatedAt?: KtDateTime | null;
|
||||
|
||||
@Column({ length: 15, name: 'last_published_public_ipv4', nullable: true })
|
||||
lastPublishedPublicIpv4?: string | null;
|
||||
|
||||
@Column({ name: 'last_published_public_port', nullable: true, type: 'int' })
|
||||
lastPublishedPublicPort?: number | null;
|
||||
|
||||
@KtDateTimeColumn({
|
||||
name: 'last_published_at',
|
||||
nullable: true,
|
||||
precision: 6,
|
||||
type: 'datetime',
|
||||
})
|
||||
lastPublishedAt?: KtDateTime | null;
|
||||
|
||||
@Column({ length: 64, name: 'last_error_code', nullable: true })
|
||||
lastErrorCode?: string | null;
|
||||
|
||||
@Column({ length: 512, name: 'last_error_message', nullable: true })
|
||||
lastErrorMessage?: string | null;
|
||||
|
||||
@Column({ length: 64, name: 'keeper_last_error_code', nullable: true })
|
||||
keeperLastErrorCode?: string | null;
|
||||
|
||||
@Column({ length: 512, name: 'keeper_last_error_message', nullable: true })
|
||||
keeperLastErrorMessage?: string | null;
|
||||
|
||||
@Column({ length: 64, name: 'natmap_last_error_code', nullable: true })
|
||||
natmapLastErrorCode?: string | null;
|
||||
|
||||
@Column({ length: 512, name: 'natmap_last_error_message', nullable: true })
|
||||
natmapLastErrorMessage?: string | null;
|
||||
|
||||
@Column({ default: false, name: 'is_deleted', type: 'boolean' })
|
||||
isDeleted: boolean;
|
||||
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import { BeforeInsert, Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
import {
|
||||
ensureSnowflakeId,
|
||||
KtCreateDateColumn,
|
||||
KtDateTime,
|
||||
KtUpdateDateColumn,
|
||||
} from '@/common';
|
||||
|
||||
@Entity('network_port_forward_group')
|
||||
export class NetworkPortForwardGroup {
|
||||
@PrimaryColumn({ type: 'bigint' })
|
||||
id: string;
|
||||
|
||||
@Column({ length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
remark?: string | null;
|
||||
|
||||
@Column({ name: 'external_port', type: 'int', unsigned: true })
|
||||
externalPort: number;
|
||||
|
||||
@Column({ name: 'internal_port', type: 'int', unsigned: true })
|
||||
internalPort: number;
|
||||
|
||||
@Column({ length: 8, name: 'protocol_mode' })
|
||||
protocolMode: string;
|
||||
|
||||
@Column({ length: 15, name: 'target_ipv4' })
|
||||
targetIpv4: string;
|
||||
|
||||
@Column({ default: false, name: 'is_deleted', type: 'boolean' })
|
||||
isDeleted: boolean;
|
||||
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 })
|
||||
createTime: KtDateTime;
|
||||
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 })
|
||||
updateTime: KtDateTime;
|
||||
|
||||
@BeforeInsert()
|
||||
createId(): string {
|
||||
return ensureSnowflakeId(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
-- Sanitized offline legacy fixture. It is never applied outside tests.
|
||||
INSERT INTO network_port_forward (
|
||||
id, name, remark, protocol, external_port, internal_port, active_key,
|
||||
target_ipv4, desired_presence, keeper_desired_enabled, probe_request_id,
|
||||
desired_revision, desired_issued_at, reported_revision, sync_status,
|
||||
keeper_status, current_public_ipv4, current_public_port, current_observed_at,
|
||||
current_valid_until, last_observed_ipv4, last_observed_port, last_observed_at,
|
||||
last_error_code, last_error_message, is_deleted, create_time, update_time
|
||||
) VALUES (
|
||||
2041600000000008213, 'Palworld UDP', 'sanitized existing UDP channel', 'udp',
|
||||
8213, 8211, 'udp:8213', '192.168.31.224', 'present', 1, 'probe-8213', 17,
|
||||
'2026-07-26 08:00:00.000000', 17, 'synced', 'active', '203.0.113.13', 8213,
|
||||
'2026-07-26 08:01:00.000000', '2026-07-26 08:06:00.000000', '203.0.113.13',
|
||||
8213, '2026-07-26 08:01:00.000000', NULL, NULL, 0,
|
||||
'2026-07-26 07:00:00.000000', '2026-07-26 08:01:00.000000'
|
||||
);
|
||||
|
||||
INSERT INTO network_endpoint_history (
|
||||
id, event_id, mapping_id, event_type, public_ipv4, public_port,
|
||||
first_observed_at, last_observed_at, occurred_at, reason, create_time
|
||||
) VALUES (
|
||||
2041600000000008214, 'network-8213-published', 2041600000000008213,
|
||||
'published', '203.0.113.13', 8213, '2026-07-26 08:01:00.000000',
|
||||
'2026-07-26 08:01:00.000000', '2026-07-26 08:01:00.000000', NULL,
|
||||
'2026-07-26 08:01:00.000000'
|
||||
);
|
||||
|
||||
INSERT INTO network_ddns_record (
|
||||
id, name, remark, record_type, source_type, port_forward_id, domain,
|
||||
sub_domain, active_key, enabled, sync_status, is_deleted, create_time,
|
||||
update_time
|
||||
) VALUES (
|
||||
2041600000000008215, 'sanitized 8213 DDNS', NULL, 'A', 'port_forward_ipv4',
|
||||
2041600000000008213, 'example.test', 'palworld',
|
||||
'A:port_forward_ipv4:2041600000000008213:example.test:palworld', 1, 'synced',
|
||||
0, '2026-07-26 08:01:00.000000', '2026-07-26 08:01:00.000000'
|
||||
);
|
||||
@ -1,4 +1,5 @@
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { getMetadataArgsStorage } from 'typeorm';
|
||||
import {
|
||||
ADMIN_PLATFORM_CONFIG_PROVIDERS,
|
||||
@ -10,17 +11,19 @@ import { NetworkDdnsRecord } from '../../../src/modules/admin/platform-config/ne
|
||||
import { NetworkDdnsService } from '../../../src/modules/admin/platform-config/network-management/network-ddns.service';
|
||||
import { NetworkDnsPodClient } from '../../../src/modules/admin/platform-config/network-management/network-dnspod.client';
|
||||
import { NetworkEndpointHistory } from '../../../src/modules/admin/platform-config/network-management/network-endpoint-history.entity';
|
||||
import { NetworkPortForwardGroup } from '../../../src/modules/admin/platform-config/network-management/network-port-forward-group.entity';
|
||||
import { NetworkPortForward } from '../../../src/modules/admin/platform-config/network-management/network-management.entity';
|
||||
import { NetworkManagementService } from '../../../src/modules/admin/platform-config/network-management/network-management.service';
|
||||
|
||||
describe('network management persistence module', () => {
|
||||
it('registers the four exact database entity tables', () => {
|
||||
it('registers the five exact database entity tables', () => {
|
||||
const tables = getMetadataArgsStorage().tables.filter((table) =>
|
||||
[
|
||||
NetworkPortForward,
|
||||
NetworkAgentState,
|
||||
NetworkEndpointHistory,
|
||||
NetworkDdnsRecord,
|
||||
NetworkPortForwardGroup,
|
||||
].includes(table.target as never),
|
||||
);
|
||||
expect(tables.map((table) => table.name).sort()).toEqual([
|
||||
@ -28,9 +31,103 @@ describe('network management persistence module', () => {
|
||||
'network_ddns_record',
|
||||
'network_endpoint_history',
|
||||
'network_port_forward',
|
||||
'network_port_forward_group',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps v2 group/channel metadata and datetime decorators in TypeORM storage', () => {
|
||||
const storage = getMetadataArgsStorage();
|
||||
const groupColumns = storage.columns.filter(
|
||||
(column) => column.target === NetworkPortForwardGroup,
|
||||
);
|
||||
const channelColumns = storage.columns.filter(
|
||||
(column) => column.target === NetworkPortForward,
|
||||
);
|
||||
const agentColumns = storage.columns.filter(
|
||||
(column) => column.target === NetworkAgentState,
|
||||
);
|
||||
const historyColumns = storage.columns.filter(
|
||||
(column) => column.target === NetworkEndpointHistory,
|
||||
);
|
||||
const featureProviders = (
|
||||
Reflect.getMetadata(MODULE_METADATA.IMPORTS, AdminPlatformConfigModule) as {
|
||||
module?: unknown;
|
||||
providers?: { provide?: unknown }[];
|
||||
}[]
|
||||
).flatMap((imported) => imported.providers ?? []);
|
||||
|
||||
expect(groupColumns.map((column) => column.options.name)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'create_time',
|
||||
'external_port',
|
||||
'internal_port',
|
||||
'protocol_mode',
|
||||
'target_ipv4',
|
||||
'update_time',
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
groupColumns.find((column) => column.propertyName === 'createTime')
|
||||
?.mode,
|
||||
).toBe('createDate');
|
||||
expect(
|
||||
groupColumns.find((column) => column.propertyName === 'updateTime')
|
||||
?.mode,
|
||||
).toBe('updateDate');
|
||||
expect(channelColumns.map((column) => column.options.name)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'active_group_protocol_key',
|
||||
'candidate_observed_at',
|
||||
'candidate_validated_at',
|
||||
'current_validated_at',
|
||||
'group_id',
|
||||
'last_observed_validated_at',
|
||||
'last_published_at',
|
||||
'natmap_desired_enabled',
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
channelColumns.find((column) => column.propertyName === 'groupId')?.options
|
||||
.type,
|
||||
).toBe('bigint');
|
||||
expect(
|
||||
channelColumns.find(
|
||||
(column) => column.propertyName === 'candidateObservedAt',
|
||||
)?.options.type,
|
||||
).toBe('datetime');
|
||||
expect(agentColumns.map((column) => column.options.name)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'desired_schema_version',
|
||||
'published_schema_version',
|
||||
'max_supported_schema_version',
|
||||
'tcp_natmap_capable',
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
historyColumns.find((column) => column.propertyName === 'mechanism')
|
||||
?.options.name,
|
||||
).toBe('mechanism');
|
||||
expect(featureProviders.map((provider) => provider.provide)).toContain(
|
||||
getRepositoryToken(NetworkPortForwardGroup),
|
||||
);
|
||||
expect(
|
||||
storage.indices.some(
|
||||
(index) =>
|
||||
index.target === NetworkPortForward &&
|
||||
index.name === 'uk_network_port_forward_active_group_protocol_key' &&
|
||||
index.unique === true,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
storage.relations.filter(
|
||||
(relation) =>
|
||||
[NetworkPortForward, NetworkPortForwardGroup].includes(
|
||||
relation.target as never,
|
||||
),
|
||||
),
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('registers the persisted service and dedicated MQTT bridge without router-specific clients', () => {
|
||||
const providers = Reflect.getMetadata(
|
||||
MODULE_METADATA.PROVIDERS,
|
||||
|
||||
142
test/admin/network-management/network-tcp-natmap-v2-sql.spec.ts
Normal file
142
test/admin/network-management/network-tcp-natmap-v2-sql.spec.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, '../../..');
|
||||
|
||||
function readNormalizedSql(relativePath: string): string {
|
||||
return readFileSync(resolve(REPO_ROOT, relativePath), 'utf8')
|
||||
.replace(/`/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function extractCreateTable(sql: string, tableName: string): string {
|
||||
const match = sql.match(
|
||||
new RegExp(
|
||||
`create table if not exists ${tableName} \\(([\\s\\S]*?)\\) engine=innodb`,
|
||||
),
|
||||
);
|
||||
|
||||
expect(match).not.toBeNull();
|
||||
return match?.[1] ?? '';
|
||||
}
|
||||
|
||||
function expectFinalSchema(sql: string): void {
|
||||
const group = extractCreateTable(sql, 'network_port_forward_group');
|
||||
const channel = extractCreateTable(sql, 'network_port_forward');
|
||||
const agent = extractCreateTable(sql, 'network_agent_state');
|
||||
const history = extractCreateTable(sql, 'network_endpoint_history');
|
||||
|
||||
for (const column of [
|
||||
'id bigint not null',
|
||||
'name varchar(100) not null',
|
||||
'remark text null',
|
||||
'external_port int unsigned not null',
|
||||
'internal_port int unsigned not null',
|
||||
'protocol_mode varchar(8) not null',
|
||||
'target_ipv4 varchar(15) not null',
|
||||
'is_deleted tinyint(1) not null default 0',
|
||||
'create_time datetime(6) not null',
|
||||
'update_time datetime(6) not null',
|
||||
]) {
|
||||
expect(group).toContain(column);
|
||||
}
|
||||
|
||||
for (const column of [
|
||||
'group_id bigint not null',
|
||||
'active_group_protocol_key varchar(64) null',
|
||||
'natmap_desired_enabled tinyint(1) not null default 0',
|
||||
"natmap_status varchar(16) not null default 'disabled'",
|
||||
'natmap_last_error_code varchar(64) null',
|
||||
'natmap_last_error_message varchar(512) null',
|
||||
'keeper_last_error_code varchar(64) null',
|
||||
'keeper_last_error_message varchar(512) null',
|
||||
'candidate_public_ipv4 varchar(15) null',
|
||||
'candidate_public_port int null',
|
||||
'candidate_observed_at datetime(6) null',
|
||||
'candidate_validated_at datetime(6) null',
|
||||
'current_validated_at datetime(6) null',
|
||||
'last_observed_validated_at datetime(6) null',
|
||||
'last_published_public_ipv4 varchar(15) null',
|
||||
'last_published_public_port int null',
|
||||
'last_published_at datetime(6) null',
|
||||
]) {
|
||||
expect(channel).toContain(column);
|
||||
}
|
||||
expect(channel).toContain(
|
||||
'unique key uk_network_port_forward_active_group_protocol_key (active_group_protocol_key)',
|
||||
);
|
||||
expect(channel).toContain(
|
||||
'key idx_network_port_forward_group (group_id, is_deleted, protocol)',
|
||||
);
|
||||
|
||||
for (const column of [
|
||||
'desired_schema_version int unsigned not null default 1',
|
||||
'published_schema_version int unsigned not null default 1',
|
||||
'max_supported_schema_version int unsigned not null default 1',
|
||||
'tcp_natmap_capable tinyint(1) not null default 0',
|
||||
]) {
|
||||
expect(agent).toContain(column);
|
||||
}
|
||||
expect(history).toContain("mechanism varchar(16) not null default 'udp_stun'");
|
||||
}
|
||||
|
||||
describe('TCP NATMap v2 schema SQL', () => {
|
||||
it('keeps clean init and refactor-v3 schema contracts aligned', () => {
|
||||
expectFinalSchema(readNormalizedSql('sql/network-management-init.sql'));
|
||||
expectFinalSchema(readNormalizedSql('sql/refactor-v3/00-full-schema.sql'));
|
||||
});
|
||||
|
||||
it('uses an additive idempotent migration with ordered group backfill', () => {
|
||||
const migration = readNormalizedSql('sql/network-tcp-natmap-v2.sql');
|
||||
|
||||
expect(migration).toContain('create table if not exists network_port_forward_group');
|
||||
expect(migration).toContain('information_schema.columns');
|
||||
expect(migration).toContain('information_schema.statistics');
|
||||
expect(migration).toContain('insert into network_port_forward_group');
|
||||
expect(migration).toContain('select channel.id, channel.name, channel.remark');
|
||||
expect(migration).toContain("'udp'");
|
||||
expect(migration).toContain('update network_port_forward channel set group_id = channel.id');
|
||||
expect(migration).toContain(
|
||||
"active_group_protocol_key = case when channel.is_deleted = 0 then concat(channel.group_id, ':', channel.protocol) else null end",
|
||||
);
|
||||
expect(migration).toContain('network_port_forward_group_id_null_count');
|
||||
expect(migration).toContain('modify column group_id bigint not null');
|
||||
|
||||
expect(
|
||||
migration.indexOf('network_port_forward_group_id_null_count'),
|
||||
).toBeLessThan(migration.indexOf('modify column group_id bigint not null'));
|
||||
expect(migration).not.toMatch(/\b(drop|truncate|delete)\b/);
|
||||
expect(migration).not.toMatch(/\b(rename\s+(table|column))\b/);
|
||||
expect(migration).not.toMatch(/foreign\s+key/);
|
||||
expect(migration).not.toMatch(
|
||||
/update network_port_forward(?:\s+\w+)?\s+set\s+(?:id|protocol|external_port|internal_port|desired_revision|reported_revision|keeper_status|current_public_ipv4|current_public_port|last_observed_ipv4|last_observed_port)\s*=/,
|
||||
);
|
||||
});
|
||||
|
||||
it('ships a read-only verification query for final constraints', () => {
|
||||
const verify = readNormalizedSql('sql/network-tcp-natmap-v2-verify.sql');
|
||||
|
||||
expect(verify).toContain('from network_port_forward_group');
|
||||
expect(verify).toContain("column_name = 'group_id'");
|
||||
expect(verify).toContain('uk_network_port_forward_active_group_protocol_key');
|
||||
expect(verify).toContain('idx_network_port_forward_group');
|
||||
expect(verify).not.toMatch(/\b(insert|update|delete|alter|drop|truncate)\b/);
|
||||
});
|
||||
|
||||
it('preserves the sanitized existing 8213 UDP channel, history, and DDNS binding', () => {
|
||||
const fixture = readNormalizedSql(
|
||||
'test/admin/network-management/fixtures/network-tcp-natmap-v2-existing-udp.sql',
|
||||
);
|
||||
|
||||
expect(fixture).toContain('2041600000000008213');
|
||||
expect(fixture).toContain("'udp', 8213, 8211, 'udp:8213'");
|
||||
expect(fixture).toContain("17, '2026-07-26 08:00:00.000000', 17, 'synced', 'active'");
|
||||
expect(fixture).toContain("'203.0.113.13', 8213");
|
||||
expect(fixture).toContain('network_endpoint_history');
|
||||
expect(fixture).toContain('network_ddns_record');
|
||||
expect(fixture).toContain("'port_forward_ipv4', 2041600000000008213");
|
||||
expect(fixture).not.toContain('tcp');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user