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():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case ev := <-u.events:
|
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)
|
u.buffer(ev)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
u.flushBuffer(client)
|
u.flushBuffer(client)
|
||||||
}
|
}
|
||||||
@@ -53,14 +60,26 @@ func (u *Uploader) buffer(ev SuspectedEvent) {
|
|||||||
func (u *Uploader) flushBuffer(client *http.Client) {
|
func (u *Uploader) flushBuffer(client *http.Client) {
|
||||||
remaining := u.buf[:0]
|
remaining := u.buf[:0]
|
||||||
for _, ev := range u.buf {
|
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)
|
remaining = append(remaining, ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u.buf = remaining
|
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)
|
url := fmt.Sprintf("%s/api/v1/edge/events/suspected", u.cfg.CloudURL)
|
||||||
body, _ := json.Marshal(ev)
|
body, _ := json.Marshal(ev)
|
||||||
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
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)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("uploader: upload error: %v", err)
|
log.Printf("uploader: upload error: %v", err)
|
||||||
return false
|
return nil // Network error, retryable
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
ok := resp.StatusCode == 200 || resp.StatusCode == 201
|
|
||||||
if !ok {
|
if resp.StatusCode == 401 || resp.StatusCode == 403 {
|
||||||
log.Printf("uploader: upload failed status=%d", resp.StatusCode)
|
return &UploadError{Fatal: true, Msg: fmt.Sprintf("unauthorized (status %d), check token", resp.StatusCode)}
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
|
if resp.StatusCode == 200 || resp.StatusCode == 201 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("uploader: upload failed status=%d", resp.StatusCode)
|
||||||
|
return nil // Server error, retryable
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user