fix(event): add 401 handling to prevent infinite retry loop on auth failure
- UploadError struct to distinguish fatal auth errors from network errors - Clear buffer and throttle on 401/403 to save bandwidth - Prevent dead-loop retry when token is invalid or expired
This commit is contained in:
11
.dockerignore
Normal file
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.git/
|
||||
.gitignore
|
||||
build/
|
||||
data/
|
||||
*.om
|
||||
*.onnx
|
||||
*.tar.gz
|
||||
logs/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
||||
# 使用华为云镜像加速的 Python 3.9 Slim
|
||||
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/python:3.9-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# 安装 ffmpeg, curl 及 OpenCV 基础依赖 (使用华为云 apt 源)
|
||||
RUN sed -i 's/deb.debian.org/repo.huaweicloud.com/g' /etc/apt/sources.list && \
|
||||
sed -i 's/security.debian.org/repo.huaweicloud.com/g' /etc/apt/sources.list && \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
ffmpeg curl libgl1 libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/tianyan
|
||||
|
||||
# 1. 复制已编译的 Go 二进制 (由宿主机构建)
|
||||
COPY build/edge-agent /opt/tianyan/edge-agent
|
||||
|
||||
# 2. 安装 Python 依赖 (使用华为云 pip 源)
|
||||
COPY python/requirements.txt .
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt -i https://repo.huaweicloud.com/repository/pypi/simple \
|
||||
&& rm -rf /root/.cache/pip
|
||||
|
||||
# 3. 复制业务代码与脚本
|
||||
COPY python/ /opt/tianyan/python/
|
||||
COPY scripts/ /opt/tianyan/scripts/
|
||||
|
||||
# 4. 创建运行时目录
|
||||
RUN mkdir -p /opt/tianyan/config /opt/tianyan/model /tmp
|
||||
|
||||
# 环境变量 (CANN 路径将由 docker-compose 挂载覆盖)
|
||||
ENV PYTHONPATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages
|
||||
ENV LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:/usr/local/Ascend/driver/lib64
|
||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
# 启动脚本
|
||||
COPY entrypoint.sh /opt/tianyan/entrypoint.sh
|
||||
RUN chmod +x /opt/tianyan/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/opt/tianyan/entrypoint.sh"]
|
||||
16
data/config/edge.yaml
Normal file
16
data/config/edge.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
device_uuid: 8541db9f77826e39605ef2c032f8fb93
|
||||
edge_id: edge-demo-001
|
||||
cloud_url: http://101.36.73.102:8004
|
||||
mqtt_broker: tcp://101.36.73.102:1883
|
||||
mqtt_user: ""
|
||||
mqtt_pass: ""
|
||||
edge_token: "be079dc5ec8d7c3796d052f3c10143cd"
|
||||
rtsp_urls:
|
||||
- "http://101.36.73.102:8080/rtp/34020000002000000003_34020000001310000001.live.flv?secret=be079dc5ec8d7c3796d052f3c10143cd"
|
||||
infer_socket: /tmp/edge-infer.sock
|
||||
infer_fps: 2
|
||||
infer_workers: 3
|
||||
conf_threshold: 0.2
|
||||
dedup_window_sec: 30
|
||||
ota_url: http://101.36.73.102:8087
|
||||
version: 1.0.0
|
||||
34
docker-compose.yml
Normal file
34
docker-compose.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
tianyan-edge:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: tianyan-edge:latest
|
||||
container_name: tianyan-edge
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
pid: "host"
|
||||
|
||||
# 显式映射 NPU 设备
|
||||
devices:
|
||||
- /dev/davinci0
|
||||
- /dev/davinci_manager
|
||||
- /dev/devmm_svm
|
||||
- /dev/hisi_hdc
|
||||
|
||||
volumes:
|
||||
# 🔑 核心:挂载宿主机 CANN 驱动与库
|
||||
- /usr/local/Ascend:/usr/local/Ascend:ro
|
||||
|
||||
# 挂载配置与模型
|
||||
- ./data/config:/opt/tianyan/config
|
||||
- ./data/model:/opt/tianyan/model
|
||||
|
||||
# 共享 Unix Socket 与日志
|
||||
- /tmp:/tmp
|
||||
- ./data/logs:/var/log/tianyan
|
||||
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
41
entrypoint.sh
Normal file
41
entrypoint.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting Tianyan Edge Container..."
|
||||
|
||||
# 1. 启动 NPU 推理服务 (后台)
|
||||
echo "[1/2] Starting infer_server.py..."
|
||||
cd /opt/tianyan
|
||||
python3 python/infer_server.py > /var/log/tianyan/infer.log 2>&1 &
|
||||
INFER_PID=$!
|
||||
|
||||
# 2. 等待 Unix Socket 创建 (最多 20s)
|
||||
echo "⏳ Waiting for inference socket at $INFER_SOCKET..."
|
||||
for i in $(seq 1 20); do
|
||||
if [ -S "${INFER_SOCKET:-/tmp/edge-infer.sock}" ]; then
|
||||
echo "✅ Socket ready. Starting edge-agent..."
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -S "${INFER_SOCKET:-/tmp/edge-infer.sock}" ]; then
|
||||
echo "❌ ERROR: Inference socket not created. Check NPU/CANN status."
|
||||
cat /var/log/tianyan/infer.log
|
||||
kill $INFER_PID 2>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. 启动 Go 边缘代理
|
||||
/opt/tianyan/edge-agent -config /opt/tianyan/config/edge.yaml > /var/log/tianyan/agent.log 2>&1 &
|
||||
AGENT_PID=$!
|
||||
|
||||
# 4. 信号捕获与优雅退出
|
||||
trap "echo 'Shutting down...'; kill $INFER_PID $AGENT_PID 2>/dev/null; wait; exit 0" SIGINT SIGTERM
|
||||
|
||||
# 阻塞主进程
|
||||
wait -n
|
||||
EXIT_CODE=$?
|
||||
echo "Process exited with code $EXIT_CODE"
|
||||
kill $INFER_PID $AGENT_PID 2>/dev/null
|
||||
exit $EXIT_CODE
|
||||
@@ -33,9 +33,16 @@ func (u *Uploader) Run(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case ev := <-u.events:
|
||||
if !u.upload(client, ev) {
|
||||
if err := u.upload(client, ev); err != nil {
|
||||
if ue, ok := err.(*UploadError); ok && ue.Fatal {
|
||||
log.Printf("uploader: FATAL: %s. Clearing buffer and stopping.", ue.Msg)
|
||||
u.buf = nil // Drop all pending events
|
||||
// Give some time before returning or loop with delay
|
||||
time.Sleep(1 * time.Minute)
|
||||
} else {
|
||||
u.buffer(ev)
|
||||
}
|
||||
}
|
||||
case <-ticker.C:
|
||||
u.flushBuffer(client)
|
||||
}
|
||||
@@ -53,14 +60,26 @@ func (u *Uploader) buffer(ev SuspectedEvent) {
|
||||
func (u *Uploader) flushBuffer(client *http.Client) {
|
||||
remaining := u.buf[:0]
|
||||
for _, ev := range u.buf {
|
||||
if !u.upload(client, ev) {
|
||||
if err := u.upload(client, ev); err != nil {
|
||||
if ue, ok := err.(*UploadError); ok && ue.Fatal {
|
||||
log.Printf("uploader: FATAL flush: %s. Dropping remaining buffer.", ue.Msg)
|
||||
u.buf = nil
|
||||
return
|
||||
}
|
||||
remaining = append(remaining, ev)
|
||||
}
|
||||
}
|
||||
u.buf = remaining
|
||||
}
|
||||
|
||||
func (u *Uploader) upload(client *http.Client, ev SuspectedEvent) bool {
|
||||
type UploadError struct {
|
||||
Fatal bool
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *UploadError) Error() string { return e.Msg }
|
||||
|
||||
func (u *Uploader) upload(client *http.Client, ev SuspectedEvent) error {
|
||||
url := fmt.Sprintf("%s/api/v1/edge/events/suspected", u.cfg.CloudURL)
|
||||
body, _ := json.Marshal(ev)
|
||||
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||||
@@ -69,12 +88,18 @@ func (u *Uploader) upload(client *http.Client, ev SuspectedEvent) bool {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("uploader: upload error: %v", err)
|
||||
return false
|
||||
return nil // Network error, retryable
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ok := resp.StatusCode == 200 || resp.StatusCode == 201
|
||||
if !ok {
|
||||
|
||||
if resp.StatusCode == 401 || resp.StatusCode == 403 {
|
||||
return &UploadError{Fatal: true, Msg: fmt.Sprintf("unauthorized (status %d), check token", resp.StatusCode)}
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 || resp.StatusCode == 201 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("uploader: upload failed status=%d", resp.StatusCode)
|
||||
}
|
||||
return ok
|
||||
return nil // Server error, retryable
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user