138 lines
4.4 KiB
Python
138 lines
4.4 KiB
Python
"""add miner control foundation
|
|
|
|
Revision ID: 0015_miner_control_foundation
|
|
Revises: 0014_repair_parts_tracking
|
|
Create Date: 2026-08-24
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
|
|
revision: str = "0015_miner_control_foundation"
|
|
down_revision: Union[str, None] = "0014_repair_parts_tracking"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
QUEUE_STATUSES = ("pending", "claimed", "completed", "failed", "cancelled")
|
|
|
|
|
|
def _format_values(values: object) -> str:
|
|
return ", ".join(str(value) for value in (values or []))
|
|
|
|
|
|
def upgrade() -> None:
|
|
connection = op.get_bind()
|
|
unknown_statuses = connection.execute(
|
|
sa.text(
|
|
"""
|
|
SELECT array_agg(DISTINCT command_status ORDER BY command_status)
|
|
FROM minerops_agent_command_queue
|
|
WHERE command_status NOT IN ('pending', 'claimed', 'completed', 'failed', 'cancelled')
|
|
"""
|
|
),
|
|
).scalar()
|
|
if unknown_statuses:
|
|
raise RuntimeError(
|
|
"Cannot upgrade to 0015: minerops_agent_command_queue contains "
|
|
f"unsupported command_status values: {_format_values(unknown_statuses)}. "
|
|
"Correct those rows to pending, claimed, completed, failed, or cancelled "
|
|
"before retrying the migration."
|
|
)
|
|
|
|
duplicate_live_miners = connection.execute(
|
|
sa.text(
|
|
"""
|
|
SELECT request_payload ->> 'miner_id' AS miner_id
|
|
FROM minerops_agent_command_queue
|
|
WHERE command_type = 'miner_control'
|
|
AND command_status IN ('pending', 'claimed')
|
|
AND nullif(trim(request_payload ->> 'miner_id'), '') IS NOT NULL
|
|
GROUP BY request_payload ->> 'miner_id'
|
|
HAVING count(*) > 1
|
|
ORDER BY request_payload ->> 'miner_id'
|
|
"""
|
|
)
|
|
).scalars().all()
|
|
if duplicate_live_miners:
|
|
raise RuntimeError(
|
|
"Cannot upgrade to 0015: more than one pending or claimed miner_control "
|
|
"command exists for miner UUID(s): "
|
|
f"{_format_values(duplicate_live_miners)}. Resolve or cancel the duplicate "
|
|
"live commands before retrying the migration; no queue history was changed."
|
|
)
|
|
|
|
op.add_column(
|
|
"minerops_site_agents",
|
|
sa.Column("site_id", postgresql.UUID(as_uuid=True), nullable=True),
|
|
)
|
|
op.create_foreign_key(
|
|
"fk_minerops_site_agents_site_id",
|
|
"minerops_site_agents",
|
|
"sites",
|
|
["site_id"],
|
|
["id"],
|
|
ondelete="RESTRICT",
|
|
)
|
|
op.create_index(
|
|
"uq_minerops_site_agents_site_id",
|
|
"minerops_site_agents",
|
|
["site_id"],
|
|
unique=True,
|
|
postgresql_where=sa.text("site_id IS NOT NULL"),
|
|
)
|
|
op.create_check_constraint(
|
|
"ck_minerops_agent_command_queue_status",
|
|
"minerops_agent_command_queue",
|
|
"command_status IN ('pending', 'claimed', 'completed', 'failed', 'cancelled')",
|
|
)
|
|
op.create_index(
|
|
"uq_agent_queue_live_miner_control",
|
|
"minerops_agent_command_queue",
|
|
[sa.text("(request_payload ->> 'miner_id')")],
|
|
unique=True,
|
|
postgresql_where=sa.text(
|
|
"command_type = 'miner_control' "
|
|
"AND command_status IN ('pending', 'claimed')"
|
|
),
|
|
)
|
|
op.create_index(
|
|
"idx_agent_queue_miner_control_history",
|
|
"minerops_agent_command_queue",
|
|
[
|
|
sa.text("(request_payload ->> 'miner_id')"),
|
|
sa.text("created_at DESC"),
|
|
sa.text("id DESC"),
|
|
],
|
|
postgresql_where=sa.text("command_type = 'miner_control'"),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index(
|
|
"idx_agent_queue_miner_control_history",
|
|
table_name="minerops_agent_command_queue",
|
|
)
|
|
op.drop_index(
|
|
"uq_agent_queue_live_miner_control",
|
|
table_name="minerops_agent_command_queue",
|
|
)
|
|
op.drop_constraint(
|
|
"ck_minerops_agent_command_queue_status",
|
|
"minerops_agent_command_queue",
|
|
type_="check",
|
|
)
|
|
op.drop_index(
|
|
"uq_minerops_site_agents_site_id",
|
|
table_name="minerops_site_agents",
|
|
)
|
|
op.drop_constraint(
|
|
"fk_minerops_site_agents_site_id",
|
|
"minerops_site_agents",
|
|
type_="foreignkey",
|
|
)
|
|
op.drop_column("minerops_site_agents", "site_id")
|