ssh туннель для БД

This commit is contained in:
dv 2026-04-10 13:54:28 +03:00
parent 67f440682f
commit 317b701afe
2 changed files with 63 additions and 7 deletions

View file

@ -710,11 +710,48 @@ async def discover_via_ssh(
)
return result
# Step 0.5: upsert DB server as a device now (before connection attempt),
# so the user can see it and configure tunnel even if connection fails.
now_pre = datetime.now(timezone.utc)
db_device_obj: Device | None = None
if db_host and db_host != obj.server_ip:
try:
db_device_obj = (await db.execute(
select(Device).where(Device.object_id == obj.id, Device.ip == db_host)
)).scalar_one_or_none()
if db_device_obj is None:
db_device_obj = Device(
object_id=obj.id,
ip=db_host,
hostname="db_server",
category="vm",
role="other",
source="auto_ssh",
location="server_room",
status="unknown",
)
db.add(db_device_obj)
await db.flush()
logger.info("[ssh_discovery] %s (db_server) - PRE-ADDED before connection attempt", db_host)
result.created += 1
result.actions.append(DeviceAction(
ip=db_host, hostname="db_server", action="ADDED",
reason="object infrastructure device (vm/db_server)", category="vm", role="other",
))
except Exception as exc:
logger.warning("[ssh_discovery] Failed to pre-upsert db_server device %s: %s", db_host, exc)
# Read tunnel flag from the device (user may have set it via UI), fall back to object setting
device_via_tunnel = bool(
(db_device_obj.connection_params or {}).get("db_via_tunnel")
) if db_device_obj else False
use_tunnel = device_via_tunnel or obj.db_via_tunnel
# Step 1: get rack hosts from object's DB
await _emit("disc_action", {"message": "Получение списка стоек из БД..."})
logger.info(
"[ssh_discovery] Connecting to object DB: host=%s port=%s dbname=%s user=%s table=%s via_tunnel=%s",
db_host, db_port, db_name, db_user, db_table, obj.db_via_tunnel,
db_host, db_port, db_name, db_user, db_table, use_tunnel,
)
try:
rack_hosts = await _get_rack_hosts_direct(
@ -724,14 +761,24 @@ async def discover_via_ssh(
password=db_password or "",
dbname=db_name,
table=db_table,
via_tunnel=obj.db_via_tunnel,
via_tunnel=use_tunnel,
tunnel_host=obj.server_ip,
tunnel_ssh_port=ssh_port,
tunnel_ssh_user=obj.ssh_user,
tunnel_ssh_password=ssh_password,
)
# Connection succeeded — mark DB device online
if db_device_obj is not None:
db_device_obj.status = "online"
db_device_obj.last_seen = now_pre
except Exception as exc:
result.errors.append(f"Cannot fetch rack hosts from object DB: {exc}")
# Mark DB device offline so the user sees it's unreachable
if db_device_obj is not None:
db_device_obj.status = "offline"
err_msg = f"Cannot fetch rack hosts from object DB: {exc}"
if "timeout" in str(exc).lower():
err_msg += ". Попробуйте настроить SSH-туннель (отредактируйте устройство БД и включите опцию)"
result.errors.append(err_msg)
return result
if not rack_hosts:
@ -774,14 +821,12 @@ async def discover_via_ssh(
rack_id_mapping[ext_id] = new_rack.id
logger.info(f"[ssh_discovery] Created rack entry '{rack_name}' rack_type={rack_type_db} (id={new_rack.id})")
# Step 1b: Add main CAPS server and DB server as devices
# Step 1b: Add main CAPS server as device (DB server was already upserted in Step 0.5)
# verified=True means we successfully connected to this host during this discovery run
now = datetime.now(timezone.utc)
infra_devices = []
if obj.server_ip:
infra_devices.append((obj.server_ip, "main_server", "caps_server", server_ip_ssh_verified))
if db_host and db_host != obj.server_ip:
infra_devices.append((db_host, "vm", "db_server", True)) # DB query succeeded = online
for infra_ip, infra_category, infra_hostname, infra_verified in infra_devices:
try:

View file

@ -78,6 +78,7 @@ export function DeviceEditModal({ open, device, objId, onClose, onSaved }: Props
ssh_user_override: device.ssh_user_override ?? '',
ssh_port_override: device.ssh_port_override ?? undefined,
ssh_key_id_override: device.ssh_key_id_override ?? undefined,
db_via_tunnel: !!(device.connection_params?.db_via_tunnel),
})
} else {
form.resetFields()
@ -86,10 +87,14 @@ export function DeviceEditModal({ open, device, objId, onClose, onSaved }: Props
const handleOk = async () => {
const values = await form.validateFields()
const payload = stripEmpty(values)
const { db_via_tunnel, ...rest } = values
const payload = stripEmpty(rest)
if ('rack_id' in values && values.rack_id === undefined) {
payload.rack_id = null
}
// Merge db_via_tunnel into connection_params
const existingParams = device?.connection_params ?? {}
payload.connection_params = { ...existingParams, db_via_tunnel: !!db_via_tunnel }
try {
let saved: DeviceItem
if (device) {
@ -213,6 +218,12 @@ export function DeviceEditModal({ open, device, objId, onClose, onSaved }: Props
options={(sshKeys ?? []).map((k) => ({ value: k.id, label: `${k.name} (${k.path})` }))}
/>
</Form.Item>
<Divider orientation="left" plain>Подключение к БД</Divider>
<Form.Item name="db_via_tunnel" valuePropName="checked">
<Checkbox>Подключаться к БД через SSH-туннель (через main_server объекта)</Checkbox>
</Form.Item>
</Form>
</Modal>
)