Merge pull request #2658 from GNS3/convert-db-fields-to-json

Convert database template fields from PickleType to JSON
This commit is contained in:
Jeremy Grossmann 2026-04-03 21:06:29 +08:00 committed by GitHub
commit 55fa7bb10c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 9 deletions

View File

@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Boolean, Column, String, Integer, Float, ForeignKey, PickleType, JSON from sqlalchemy import Boolean, Column, String, Integer, Float, ForeignKey, JSON
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from .base import BaseTable, generate_uuid, GUID from .base import BaseTable, generate_uuid, GUID
@ -51,7 +51,7 @@ class CloudTemplate(Template):
__tablename__ = "cloud_templates" __tablename__ = "cloud_templates"
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True) template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
ports_mapping = Column(PickleType) ports_mapping = Column(JSON)
remote_console_host = Column(String) remote_console_host = Column(String)
remote_console_port = Column(Integer) remote_console_port = Column(Integer)
remote_console_type = Column(String) remote_console_type = Column(String)
@ -77,10 +77,10 @@ class DockerTemplate(Template):
console_http_path = Column(String) console_http_path = Column(String)
console_resolution = Column(String) console_resolution = Column(String)
extra_hosts = Column(String) extra_hosts = Column(String)
extra_volumes = Column(PickleType) extra_volumes = Column(JSON)
memory = Column(Integer) memory = Column(Integer)
cpus = Column(Float) cpus = Column(Float)
custom_adapters = Column(PickleType) custom_adapters = Column(JSON)
__mapper_args__ = {"polymorphic_identity": "docker", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "docker", "polymorphic_load": "selectin"}
@ -133,7 +133,7 @@ class EthernetHubTemplate(Template):
__tablename__ = "ethernet_hub_templates" __tablename__ = "ethernet_hub_templates"
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True) template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
ports_mapping = Column(PickleType) ports_mapping = Column(JSON)
__mapper_args__ = {"polymorphic_identity": "ethernet_hub", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "ethernet_hub", "polymorphic_load": "selectin"}
@ -143,7 +143,7 @@ class EthernetSwitchTemplate(Template):
__tablename__ = "ethernet_switch_templates" __tablename__ = "ethernet_switch_templates"
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True) template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
ports_mapping = Column(PickleType) ports_mapping = Column(JSON)
console_type = Column(String) console_type = Column(String)
__mapper_args__ = {"polymorphic_identity": "ethernet_switch", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "ethernet_switch", "polymorphic_load": "selectin"}
@ -211,7 +211,7 @@ class QemuTemplate(Template):
cpu_throttling = Column(Integer) cpu_throttling = Column(Integer)
process_priority = Column(String) process_priority = Column(String)
options = Column(String) options = Column(String)
custom_adapters = Column(PickleType) custom_adapters = Column(JSON)
__mapper_args__ = {"polymorphic_identity": "qemu", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "qemu", "polymorphic_load": "selectin"}
@ -234,7 +234,7 @@ class VirtualBoxTemplate(Template):
on_close = Column(String) on_close = Column(String)
console_type = Column(String) console_type = Column(String)
console_auto_start = Column(Boolean) console_auto_start = Column(Boolean)
custom_adapters = Column(PickleType) custom_adapters = Column(JSON)
__mapper_args__ = {"polymorphic_identity": "virtualbox", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "virtualbox", "polymorphic_load": "selectin"}
@ -256,7 +256,7 @@ class VMwareTemplate(Template):
on_close = Column(String) on_close = Column(String)
console_type = Column(String) console_type = Column(String)
console_auto_start = Column(Boolean) console_auto_start = Column(Boolean)
custom_adapters = Column(PickleType) custom_adapters = Column(JSON)
__mapper_args__ = {"polymorphic_identity": "vmware", "polymorphic_load": "selectin"} __mapper_args__ = {"polymorphic_identity": "vmware", "polymorphic_load": "selectin"}

View File

@ -0,0 +1,134 @@
"""convert PickleType fields to JSON
Revision ID: ec4b7b198555
Revises: 20260303_create_llm_model_configs
Create Date: 2026-04-03 19:51:06.173013
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ec4b7b198555'
down_revision = '20260303_create_llm_model_configs'
branch_labels = None
depends_on = None
def convert_pickle_to_json(conn, table_name: str, column_name: str) -> None:
"""
Convert a PickleType column to JSON for all rows in a table.
"""
import pickle
import json
result = conn.execute(sa.text(f"SELECT template_id, {column_name} FROM {table_name}"))
for row in result:
column_data = getattr(row, column_name)
if column_data:
# Unpickle and convert to JSON
data = pickle.loads(column_data)
if data:
json_data = json.dumps(data)
conn.execute(
sa.text(f"UPDATE {table_name} SET {column_name} = :data WHERE template_id = :template_id"),
{"data": json_data, "template_id": row.template_id}
)
else:
# Set NULL if there is no data to be converted
conn.execute(
sa.text(f"UPDATE {table_name} SET {column_name} = NULL WHERE template_id = :template_id"),
{"template_id": row.template_id}
)
def convert_json_to_pickle(conn, table_name: str, column_name: str) -> None:
"""
Convert a JSON column to PickleType for all rows in a table.
"""
import pickle
import json
result = conn.execute(sa.text(f"SELECT template_id, {column_name} FROM {table_name}"))
for row in result:
column_data = getattr(row, column_name)
if column_data:
# Parse JSON and convert to pickle
data = json.loads(column_data)
if data:
pickle_data = pickle.dumps(data)
conn.execute(
sa.text(f"UPDATE {table_name} SET {column_name} = :data WHERE template_id = :template_id"),
{"data": pickle_data, "template_id": row.template_id}
)
else:
# Set NULL if there is no data to be converted
conn.execute(
sa.text(f"UPDATE {table_name} SET {column_name} = NULL WHERE template_id = :template_id"),
{"template_id": row.template_id}
)
def upgrade() -> None:
# Get connection
conn = op.get_bind()
# Convert PickleType fields to JSON
convert_pickle_to_json(conn, "cloud_templates", "ports_mapping")
convert_pickle_to_json(conn, "docker_templates", "extra_volumes")
convert_pickle_to_json(conn, "docker_templates", "custom_adapters")
convert_pickle_to_json(conn, "ethernet_hub_templates", "ports_mapping")
convert_pickle_to_json(conn, "ethernet_switch_templates", "ports_mapping")
convert_pickle_to_json(conn, "qemu_templates", "custom_adapters")
convert_pickle_to_json(conn, "virtualbox_templates", "custom_adapters")
convert_pickle_to_json(conn, "vmware_templates", "custom_adapters")
with op.batch_alter_table('cloud_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.JSON())
with op.batch_alter_table('docker_templates') as batch_op:
batch_op.alter_column('extra_volumes', type_=sa.JSON())
batch_op.alter_column('custom_adapters', type_=sa.JSON())
with op.batch_alter_table('ethernet_hub_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.JSON())
with op.batch_alter_table('ethernet_switch_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.JSON())
with op.batch_alter_table('qemu_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.JSON())
with op.batch_alter_table('virtualbox_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.JSON())
with op.batch_alter_table('vmware_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.JSON())
def downgrade() -> None:
# Get connection
conn = op.get_bind()
# Convert JSON fields back to PickleType
convert_json_to_pickle(conn, "cloud_templates", "ports_mapping")
convert_json_to_pickle(conn, "docker_templates", "extra_volumes")
convert_json_to_pickle(conn, "docker_templates", "custom_adapters")
convert_json_to_pickle(conn, "ethernet_hub_templates", "ports_mapping")
convert_json_to_pickle(conn, "ethernet_switch_templates", "ports_mapping")
convert_json_to_pickle(conn, "qemu_templates", "custom_adapters")
convert_json_to_pickle(conn, "virtualbox_templates", "custom_adapters")
convert_json_to_pickle(conn, "vmware_templates", "custom_adapters")
with op.batch_alter_table('cloud_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.PickleType())
with op.batch_alter_table('docker_templates') as batch_op:
batch_op.alter_column('extra_volumes', type_=sa.PickleType())
batch_op.alter_column('custom_adapters', type_=sa.PickleType())
with op.batch_alter_table('ethernet_hub_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.PickleType())
with op.batch_alter_table('ethernet_switch_templates') as batch_op:
batch_op.alter_column('ports_mapping', type_=sa.PickleType())
with op.batch_alter_table('qemu_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.PickleType())
with op.batch_alter_table('virtualbox_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.PickleType())
with op.batch_alter_table('vmware_templates') as batch_op:
batch_op.alter_column('custom_adapters', type_=sa.PickleType())