jdeip/app/rotation_service.py

216 lines
6.3 KiB
Python
Raw Normal View History

2025-10-21 18:41:07 +08:00
"""
IP轮换服务模块
提供IP地址轮换功能包括
- 从可用设备中选择未使用的IP
- 配置网关路由规则
- 执行IP轮换操作
- 查询当前状态和统计信息
使用Redis存储使用记录支持按天统计IP使用情况
"""
import random
from typing import Any, Dict, List, Optional, Tuple
2025-10-21 18:41:07 +08:00
from .config import settings, city_dict, client_infos
from .eip_client import client_singleton as eip
from .redis_store import store_singleton as kv
def _extract_device_ip_and_id(device: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
2025-10-21 18:41:07 +08:00
"""
从设备信息中提取IP地址和边缘设备ID
由于后端返回结构可能不一致使用多种字段名进行容错提取
- IP字段ip, public_ip, eip, addr, address
- ID字段id, edge, mac, device_id
Args:
device: 设备信息字典
Returns:
Tuple[Optional[str], Optional[str]]: (IP地址, 边缘设备ID)
"""
# 尝试多种可能的IP字段名
ip = (
2025-10-21 18:41:07 +08:00
device.get("public")
)
2025-10-21 18:41:07 +08:00
macaddr = device.get("macaddr")
return (str(ip), str(macaddr))
def select_unused_ip(devices: List[Dict[str, Any]]) -> Tuple[Optional[str], Optional[str]]:
2025-10-21 18:41:07 +08:00
"""
从设备列表中选择今天未使用的IP地址
遍历设备列表找到第一个今天未使用的IP地址和对应的边缘设备ID
Args:
devices: 设备信息列表
Returns:
Tuple[Optional[str], Optional[str]]: (IP地址, 边缘设备ID)如果没找到则返回(None, None)
"""
for d in devices.get('edges'):
ip, edge_id = _extract_device_ip_and_id(d)
if not ip:
2025-10-21 18:41:07 +08:00
# 没有可识别的IP跳过此设备
continue
if not kv.is_ip_used_today(ip):
return ip, edge_id
return None, None
2025-10-21 18:41:07 +08:00
def apply_gateway_route(edge_id: Optional[str], ip: str, geo: str, client_id:str) -> Dict[str, Any]:
"""
将选中的边缘设备配置到网关路由规则中
创建路由规则并应用到指定的网关设备实现IP轮换
Args:
edge_id: 边缘设备ID
ip: IP地址用于日志记录实际路由基于edge_id
Returns:
Dict[str, Any]: 网关配置设置结果
"""
# 创建路由规则配置
rule = {
2025-10-21 18:41:07 +08:00
"table": 1, # 路由表ID
"enable": True, # 启用规则
"edge": [edge_id] if edge_id else [], # 边缘设备列表
"network": [client_infos[str(client_id)]], # 网络配置(当前为空)
2025-10-21 18:41:07 +08:00
"cityhash": geo or "", # 城市哈希值
}
2025-10-21 18:41:07 +08:00
config = {"id": 1, "rules": [rule]} # 配置ID和规则列表
return eip.gateway_config_set(settings.eip_gateway_mac, config)
2025-10-21 18:41:07 +08:00
def rotate(client_id,cities) -> Dict[str, Any]:
2025-10-21 18:41:07 +08:00
"""
执行IP轮换操作
从指定地理位置获取设备列表选择未使用的IP进行轮换
Args:
cityhash: 城市哈希值用于指定地理位置
num: 获取设备数量默认为配置值或10
Returns:
Dict[str, Any]: 轮换结果
- changed: bool - 是否成功轮换
- reason: str - 失败原因当changed=False时
- ip: str - 新的IP地址当changed=True时
- edge: str - 边缘设备ID当changed=True时
- status: dict - 网关状态信息当changed=True时
Raises:
ValueError: 当cityhash为空时
"""
# 确定地理位置参数
geo = get_random_cityhash(cities)
2025-10-21 18:41:07 +08:00
# 确定获取设备数量
n = 1000
2025-10-21 18:41:07 +08:00
# 获取设备列表
devices = eip.list_devices(geo=geo, offset=0, num=n)
2025-10-21 18:41:07 +08:00
# 选择未使用的IP
ip, edge_id = select_unused_ip(devices)
if not ip:
return {"changed": False, "reason": "没有可用且今天未使用的 IP"}
2025-10-21 18:41:07 +08:00
# 应用网关路由配置
_ = apply_gateway_route(edge_id=edge_id, ip=ip, geo= geo,client_id=client_id)
# 记录使用情况
kv.add_used_ip_today(ip) # 标记IP为今天已使用
kv.set_current(ip=ip, edge_id=edge_id) # 设置当前使用的IP和设备
# 获取网关状态
status = eip.gateway_status(settings.eip_gateway_mac)
2025-10-21 18:41:07 +08:00
return {"changed": True, "ip": ip, "edge": edge_id, "status": status, "geo": geo}
def citie_list():
return {"data":list(city_dict.keys())}
def status() -> Dict[str, Any]:
2025-10-21 18:41:07 +08:00
"""
获取当前轮换服务状态
返回当前使用的IP今日使用统计和网关状态信息
Returns:
Dict[str, Any]: 状态信息
- current: dict - 当前使用的IP和设备信息
- used_today: int - 今日已使用的IP数量
- gateway: dict - 网关状态信息如果获取失败则为空字典
"""
# 获取当前使用的IP和设备信息
cur = kv.get_current()
2025-10-21 18:41:07 +08:00
# 获取今日使用统计
used_count = kv.get_used_count_today()
2025-10-21 18:41:07 +08:00
# 获取网关状态(容错处理)
gw = {}
try:
gw = eip.gateway_status(settings.eip_gateway_mac)
except Exception:
2025-10-21 18:41:07 +08:00
# 网关状态获取失败时使用空字典
pass
2025-10-21 18:41:07 +08:00
return {"current": cur, "used_today": used_count, "gateway": gw}
def get_random_city(city_list) -> Dict[str, str]:
2025-10-21 18:41:07 +08:00
"""
随机获取一个城市编码
从所有可用的城市中随机选择一个返回城市名称和对应的编码
Returns:
Dict[str, str]: 包含城市信息的字典
- province: 省份名称
- city: 城市名称
- cityhash: 城市编码
"""
# 收集所有城市信息
all_cities = []
for province, cities in city_dict.items():
if province in city_list:
for city, cityhash in cities.items():
all_cities.append({
"province": province,
"city": city,
"cityhash": cityhash
})
2025-10-21 18:41:07 +08:00
# 随机选择一个城市
if not all_cities:
raise ValueError("没有可用的城市数据")
selected_city = random.choice(all_cities)
return selected_city
def get_random_cityhash(cities) -> str:
2025-10-21 18:41:07 +08:00
"""
随机获取一个城市编码简化版本
Returns:
str: 随机城市编码
"""
city_info = get_random_city(cities)
2025-10-21 18:41:07 +08:00
return city_info["cityhash"]