```
feat(api): 添加OLT端口管理和设备发现功能 - 在check.py中更新discover_olt方法,使用scan_and_discover替代check_olt_devices - 在olt.py中添加get_olt_ports和toggle_olt_port接口,支持获取和控制OLT端口状态 - 添加TogglePortRequest模型用于端口操作请求验证 - 更新SSHService类,新增get_olt_ports和toggle_olt_port方法 - 优化CheckService中的MAC地址索引逻辑,支持模型信息更新 ```
This commit is contained in:
@@ -80,7 +80,7 @@ def discover_olt(olt_id: int, db: Session = Depends(get_db)):
|
||||
"""扫描单台 OLT 并将新发现的 MAC 自动入库关联"""
|
||||
try:
|
||||
service = CheckService(db)
|
||||
result = asyncio.run(service.check_olt_devices(olt_id))
|
||||
result = service.scan_and_discover(olt_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -400,3 +400,46 @@ def loopback_detection(db: Session = Depends(get_db)):
|
||||
|
||||
# 按原始顺序返回
|
||||
return [results_map[olt.id] for olt in olts]
|
||||
|
||||
|
||||
class TogglePortRequest(BaseModel):
|
||||
action: str # "shutdown" 或 "undo shutdown"
|
||||
|
||||
|
||||
@router.get("/devices/{olt_id}/ports")
|
||||
def get_olt_ports(olt_id: int, db: Session = Depends(get_db)):
|
||||
"""获取指定 OLT 的所有 Olt 端口状态"""
|
||||
from app.services.ssh_service import SSHService
|
||||
olt = db.query(OLTDevice).filter(OLTDevice.id == olt_id).first()
|
||||
if not olt:
|
||||
raise HTTPException(status_code=404, detail="OLT 不存在")
|
||||
ssh = SSHService(olt.ip_address, olt.username, olt.password)
|
||||
try:
|
||||
ssh.connect()
|
||||
ports = ssh.get_olt_ports()
|
||||
return {"ports": ports}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
ssh.close()
|
||||
|
||||
|
||||
@router.post("/devices/{olt_id}/ports/toggle")
|
||||
def toggle_olt_port(olt_id: int, body: TogglePortRequest, port_name: str, db: Session = Depends(get_db)):
|
||||
"""开启或关闭指定 OLT 端口"""
|
||||
from app.services.ssh_service import SSHService
|
||||
if body.action not in ("shutdown", "undo shutdown"):
|
||||
raise HTTPException(status_code=400, detail="action 必须为 shutdown 或 undo shutdown")
|
||||
olt = db.query(OLTDevice).filter(OLTDevice.id == olt_id).first()
|
||||
if not olt:
|
||||
raise HTTPException(status_code=404, detail="OLT 不存在")
|
||||
ssh = SSHService(olt.ip_address, olt.username, olt.password)
|
||||
try:
|
||||
ssh.connect()
|
||||
ssh.toggle_olt_port(port_name, body.action)
|
||||
return {"message": f"端口 {port_name} 已{'关闭' if body.action == 'shutdown' else '开启'}"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
ssh.close()
|
||||
|
||||
|
||||
@@ -39,17 +39,18 @@ class CheckService:
|
||||
online_count = 0
|
||||
offline_count = 0
|
||||
|
||||
# 全局 MAC → id 索引,只取需要的字段
|
||||
# 全局 MAC → (id, model) 索引
|
||||
existing = {
|
||||
row.mac_address.lower(): row.id
|
||||
for row in self.db.query(ONUDevice.mac_address, ONUDevice.id).all()
|
||||
row.mac_address.lower(): (row.id, row.model)
|
||||
for row in self.db.query(ONUDevice.mac_address, ONUDevice.id, ONUDevice.model).all()
|
||||
}
|
||||
|
||||
for mac, onu_info in onu_info_dict.items():
|
||||
onu_id = existing.get(mac)
|
||||
if onu_id is None:
|
||||
entry = existing.get(mac)
|
||||
if entry is None:
|
||||
continue # 不在库中,跳过
|
||||
|
||||
onu_id, onu_model = entry
|
||||
status = onu_info.status
|
||||
distance_m = parse_distance(onu_info.distance_str)
|
||||
if status == 'online':
|
||||
@@ -57,6 +58,9 @@ class CheckService:
|
||||
else:
|
||||
offline_count += 1
|
||||
|
||||
if onu_info.model and not onu_model:
|
||||
self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update({"model": onu_info.model})
|
||||
|
||||
self.db.add(DeviceStatusHistory(
|
||||
onu_device_id=onu_id,
|
||||
status=status,
|
||||
@@ -88,32 +92,30 @@ class CheckService:
|
||||
raise Exception("关联的 OLT 不存在")
|
||||
|
||||
mac = device.mac_address.lower()
|
||||
base_cmd = olt.slot_command # e.g. "display onu slot"
|
||||
if device.slot_number is not None:
|
||||
cmd = f"{base_cmd} {device.slot_number} | include {mac}"
|
||||
else:
|
||||
cmd = f"{base_cmd} | include {mac}"
|
||||
cmd = f"{olt.slot_command} | include {mac}"
|
||||
|
||||
ssh = SSHService(olt.ip_address, olt.username, olt.password)
|
||||
try:
|
||||
ssh.connect()
|
||||
output = ssh.execute_command(cmd)
|
||||
|
||||
# 从输出中找到包含该 MAC 的行并解析
|
||||
# 跳过命令回显行(含 MAC 但无端口标识),只接受有 port_id 的行
|
||||
info = None
|
||||
for line in output.splitlines():
|
||||
if mac in line.lower():
|
||||
info = ssh._parse_device_line(line, device.slot_number)
|
||||
if info:
|
||||
parsed = ssh._parse_device_line(line, device.slot_number)
|
||||
if parsed and parsed.port_id:
|
||||
info = parsed
|
||||
break
|
||||
|
||||
if info is None:
|
||||
# 未找到该 MAC,视为离线
|
||||
status = 'offline'
|
||||
distance_m = None
|
||||
else:
|
||||
status = info.status
|
||||
distance_m = parse_distance(info.distance_str)
|
||||
if info.model and not device.model:
|
||||
device.model = info.model
|
||||
|
||||
self.db.add(DeviceStatusHistory(
|
||||
onu_device_id=device.id,
|
||||
@@ -123,7 +125,7 @@ class CheckService:
|
||||
response_data=None,
|
||||
))
|
||||
self.db.commit()
|
||||
return {"status": status, "distance_m": distance_m}
|
||||
return {"status": status, "distance_m": distance_m, "model": device.model}
|
||||
finally:
|
||||
ssh.close()
|
||||
|
||||
@@ -147,14 +149,14 @@ class CheckService:
|
||||
new_count = 0
|
||||
|
||||
existing = {
|
||||
row.mac_address.lower(): row.id
|
||||
for row in self.db.query(ONUDevice.mac_address, ONUDevice.id).all()
|
||||
row.mac_address.lower(): (row.id, row.model)
|
||||
for row in self.db.query(ONUDevice.mac_address, ONUDevice.id, ONUDevice.model).all()
|
||||
}
|
||||
|
||||
for mac, onu_info in onu_info_dict.items():
|
||||
onu_id = existing.get(mac)
|
||||
entry = existing.get(mac)
|
||||
|
||||
if onu_id is None:
|
||||
if entry is None:
|
||||
# 新设备:只入库在线的
|
||||
if onu_info.status != 'online':
|
||||
continue
|
||||
@@ -171,8 +173,12 @@ class CheckService:
|
||||
self.db.add(onu)
|
||||
self.db.flush()
|
||||
self.db.add(NewDevice(onu_device_id=onu.id, olt_id=olt_id))
|
||||
onu_id = onu.id
|
||||
new_count += 1
|
||||
onu_id = onu.id
|
||||
else:
|
||||
onu_id, onu_model = entry
|
||||
if onu_info.model and not onu_model:
|
||||
self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update({"model": onu_info.model})
|
||||
|
||||
status = onu_info.status
|
||||
distance_m = parse_distance(onu_info.distance_str)
|
||||
|
||||
@@ -276,6 +276,66 @@ class SSHService:
|
||||
model=model
|
||||
)
|
||||
|
||||
def get_olt_ports(self) -> list:
|
||||
"""获取所有 Olt 端口状态,返回 [{'name': 'Olt1/0/1', 'status': 'up'}, ...]"""
|
||||
output = self.execute_command("display interface brief")
|
||||
ports = []
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if not line.startswith("Olt"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
name = parts[0]
|
||||
link = parts[1].upper()
|
||||
# ADM 表示 administratively down(手动关闭)
|
||||
if link == "ADM":
|
||||
status = "adm-down"
|
||||
elif link == "UP":
|
||||
status = "up"
|
||||
else:
|
||||
status = "down"
|
||||
ports.append({"name": name, "status": status})
|
||||
return ports
|
||||
|
||||
def toggle_olt_port(self, port_name: str, action: str) -> bool:
|
||||
"""开启或关闭 OLT 端口
|
||||
action: 'shutdown' 或 'undo shutdown'
|
||||
流程: system-view -> interface {port_name} -> shutdown/undo shutdown -> quit -> quit
|
||||
"""
|
||||
if not self.shell:
|
||||
raise Exception("SSH 未连接")
|
||||
|
||||
def send_and_wait(cmd: str, expect: str, timeout: int = 10) -> str:
|
||||
self.shell.send(cmd + "\n")
|
||||
buf = ""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if self.shell.recv_ready():
|
||||
buf += self.shell.recv(4096).decode('utf-8', errors='ignore')
|
||||
if expect in buf:
|
||||
return buf
|
||||
else:
|
||||
time.sleep(0.2)
|
||||
return buf
|
||||
|
||||
out = send_and_wait("system-view", "]")
|
||||
if "]" not in out:
|
||||
raise Exception("进入 system-view 失败")
|
||||
|
||||
out = send_and_wait(f"interface {port_name}", "]")
|
||||
if "]" not in out:
|
||||
raise Exception(f"进入端口 {port_name} 失败")
|
||||
|
||||
out = send_and_wait(action, "]")
|
||||
if "]" not in out:
|
||||
raise Exception(f"执行 {action} 失败")
|
||||
|
||||
send_and_wait("quit", "]", timeout=5)
|
||||
send_and_wait("quit", ">", timeout=5)
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""关闭 SSH 连接"""
|
||||
if self.client:
|
||||
|
||||
Reference in New Issue
Block a user