fix: 账号列表与环境解耦;网关清理收敛已消失的旧代
- 社媒账号列表页不再拉取运行环境数据:账号是资源池,环境故障 不应阻塞账号展示,409 横幅随之消失;「固定资源」「可调度」 两列移除,绑定状态去运行环境页查看。详情页保持原行为。 - docker-gateway DELETE 清理:容器与登记网络均已不存在时返回 204 而非 409,控制面 runtime_cleanup_pending 可收敛,修复 外部清理过的旧代永久卡死;容器存活而网络消失仍 fail-closed。
This commit is contained in:
@@ -714,7 +714,16 @@ func (api gateway) remove(c fiber.Ctx) error {
|
||||
if networkExists {
|
||||
err = api.removeTenantNetwork(id, input.BindingVersion, input.RuntimeID, networkGeneration, &input, containerID, exists)
|
||||
} else if expectedNetworkID != "" {
|
||||
return writeError(c, http.StatusConflict, errGenerationConflict)
|
||||
if exists {
|
||||
// 容器仍在但登记的隔离网络已消失:代状态异常,fail-closed 交由人工对账。
|
||||
return writeError(c, http.StatusConflict, errGenerationConflict)
|
||||
}
|
||||
// 容器与登记网络均已不存在:请求指向的代在 Docker 侧已无残留,清理视为完成。
|
||||
// 否则被外部清理过的旧代会永远 409,控制面的 runtime_cleanup_pending 无法收敛。
|
||||
if !api.proxies.remove(id, input.BindingVersion, input.RuntimeID, input.NetworkID) {
|
||||
return writeError(c, http.StatusConflict, errGenerationConflict)
|
||||
}
|
||||
return c.SendStatus(http.StatusNoContent)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, errGenerationConflict) {
|
||||
|
||||
@@ -1263,6 +1263,60 @@ func TestGatewayDeleteWithoutContainerOrNetworkGenerationFailsClosed(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayDeleteConvergesWhenGenerationAlreadyGone(t *testing.T) {
|
||||
networkRequests := 0
|
||||
server := httptest.NewServer(withAliasReservations("gateway-self", func(response http.ResponseWriter, request *http.Request) {
|
||||
switch {
|
||||
case request.Method == http.MethodGet && request.URL.Path == "/containers/"+namePrefix+"account-a/json":
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
case request.Method == http.MethodGet && request.URL.Path == "/networks/network-old":
|
||||
networkRequests++
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
case strings.HasPrefix(request.URL.Path, "/networks/"):
|
||||
t.Fatalf("network lookup leaked beyond the requested generation: %s %s", request.Method, request.URL.Path)
|
||||
default:
|
||||
t.Fatalf("unexpected Docker request %s %s", request.Method, request.URL.String())
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
handler := newGatewayWithSelf(dockerClient{baseURL: server.URL, client: server.Client(), slow: server.Client()},
|
||||
"creatorhub_browser", testToken, "gateway-self")
|
||||
response := httptest.NewRecorder()
|
||||
body := `{"binding_version":1,"runtime_id":"container-old","network_id":"network-old"}`
|
||||
adaptor.FiberApp(handler).ServeHTTP(response, authed(http.MethodDelete, "/v1/browsers/account-a", strings.NewReader(body)))
|
||||
if response.Code != http.StatusNoContent || networkRequests != 1 {
|
||||
t.Fatalf("already-gone generation did not converge: status=%d networkRequests=%d body=%s",
|
||||
response.Code, networkRequests, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayDeleteFailsClosedWhenContainerOutlivesNetwork(t *testing.T) {
|
||||
containerDeleted := false
|
||||
server := httptest.NewServer(withAliasReservations("gateway-self", func(response http.ResponseWriter, request *http.Request) {
|
||||
switch {
|
||||
case request.Method == http.MethodGet && request.URL.Path == "/containers/"+namePrefix+"account-a/json":
|
||||
_, _ = response.Write([]byte(`{"Id":"container-old","Config":{"Labels":{"` + managedLabel + `":"true","` + idLabel + `":"account-a","` + bindingVersionLabel + `":"1","` + networkIDLabel + `":"network-old"}}}`))
|
||||
case request.Method == http.MethodGet && request.URL.Path == "/networks/network-old":
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
case request.Method == http.MethodDelete && strings.HasPrefix(request.URL.Path, "/containers/"):
|
||||
containerDeleted = true
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
t.Fatalf("unexpected Docker request %s %s", request.Method, request.URL.String())
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
handler := newGatewayWithSelf(dockerClient{baseURL: server.URL, client: server.Client(), slow: server.Client()},
|
||||
"creatorhub_browser", testToken, "gateway-self")
|
||||
response := httptest.NewRecorder()
|
||||
body := `{"binding_version":1,"runtime_id":"container-old","network_id":"network-old"}`
|
||||
adaptor.FiberApp(handler).ServeHTTP(response, authed(http.MethodDelete, "/v1/browsers/account-a", strings.NewReader(body)))
|
||||
if response.Code != http.StatusConflict || containerDeleted {
|
||||
t.Fatalf("container outliving its network must fail closed: status=%d containerDeleted=%v body=%s",
|
||||
response.Code, containerDeleted, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayRejectsStaleProxyRestoreAfterReplacementGeneration(t *testing.T) {
|
||||
type dockerState struct {
|
||||
sync.Mutex
|
||||
|
||||
Reference in New Issue
Block a user