From 2065fd67920b92d546bf781b85e3c0df30e7527c Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 12:39:29 +0000 Subject: [PATCH 01/19] fix(vermeer): authenticate PD requests and stop failed graph loads --- .github/workflows/vermeer-ci.yml | 5 ++ vermeer/README.md | 9 +++ vermeer/apps/common/hugegraph_tools.go | 35 ++++++++-- vermeer/apps/common/pd_authority_test.go | 65 +++++++++++++++++++ vermeer/apps/graphio/hugegraph.go | 21 +++--- vermeer/apps/structure/graph.go | 9 ++- vermeer/apps/structure/graph_data.go | 9 ++- vermeer/apps/structure/vertex.go | 14 ++-- vermeer/apps/structure/vertex_init_test.go | 74 ++++++++++++++++++++++ vermeer/apps/worker/load_graph_bl.go | 23 ++++++- 10 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 vermeer/apps/common/pd_authority_test.go create mode 100644 vermeer/apps/structure/vertex_init_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index 777ecfcec..f707498f6 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -72,6 +72,11 @@ jobs: - name: Generate embedded assets run: cd asset && go generate + - name: Test PD credentials and graph initialization failures + run: | + go test -race ./apps/common + go test -race ./apps/structure -run 'Test(VertexStoreInitializationErrorAndRetry|GraphPropagatesVertexStoreInitializationError|MemoryVertexInitialization)$' + - name: Build run: CGO_ENABLED=0 go build -o vermeer diff --git a/vermeer/README.md b/vermeer/README.md index 97cf58d71..35daa24a4 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -269,6 +269,15 @@ The generated token can be used in two ways: ### Worker Configuration (`worker.ini`) +For a PD deployment using a shared secret, configure both Master and Worker with +`pd_password_file=/path/to/pd-password`, or set `VERMEER_PD_PASSWORD_FILE` to that +file. Its contents must match PD's `auth.secret-key`; protect the file with mode +`0600`. The environment variable takes precedence. A configured unreadable or +empty file fails the PD request instead of using legacy credentials. The secret +is sent as the internal `vermeer` credential and is never added to task parameters. +When no file is configured, the legacy credential remains available for older PD +deployments; it does not authenticate to a modern PD with a different shared secret. + ```ini [default] # Worker HTTP listen address diff --git a/vermeer/apps/common/hugegraph_tools.go b/vermeer/apps/common/hugegraph_tools.go index c403fa3a4..8aa5718c7 100644 --- a/vermeer/apps/common/hugegraph_tools.go +++ b/vermeer/apps/common/hugegraph_tools.go @@ -19,11 +19,13 @@ package common import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" "io" "net/http" + "os" "strconv" "strings" "sync" @@ -71,8 +73,12 @@ func findValidPD(pdIPAddress []string) (string, error) { wg := &sync.WaitGroup{} var pdIPAddr string tempCtx, tempCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer tempCancel() pdAuthority := PDAuthority{} - tempCtx = pdAuthority.SetAuthority(tempCtx) + tempCtx, err := pdAuthority.SetAuthority(tempCtx) + if err != nil { + return "", err + } for _, addr := range pdIPAddress { wg.Add(1) go func(ctx context.Context, cancel context.CancelFunc, addr string) { @@ -129,7 +135,10 @@ func findServerAddr(pdIPAddress string, hgName string, username string, password } defer pdConn.Close() pdAuthority := PDAuthority{} - ctx = pdAuthority.SetAuthority(ctx) + ctx, err = pdAuthority.SetAuthority(ctx) + if err != nil { + return "", err + } pdClient := pd.NewDiscoveryServiceClient(pdConn) resp, err := pdClient.GetNodes(ctx, &pd.Query{}) if err != nil { @@ -320,18 +329,34 @@ type PDAuthority struct { locker sync.RWMutex } -func (pa *PDAuthority) SetAuthority(ctx context.Context) context.Context { +func (pa *PDAuthority) SetAuthority(ctx context.Context) (context.Context, error) { pa.locker.RLock() defer pa.locker.RUnlock() + credential := CredentialValue // Legacy PD compatibility when no file is configured. + passwordFile := os.Getenv("VERMEER_PD_PASSWORD_FILE") + if passwordFile == "" { + passwordFile, _ = GetConfigDefault("pd_password_file", "").(string) + } + if passwordFile != "" { + password, err := os.ReadFile(passwordFile) + if err != nil { + return ctx, fmt.Errorf("read PD password file: %w", err) + } + secret := strings.TrimSuffix(strings.TrimSuffix(string(password), "\n"), "\r") + if secret == "" { + return ctx, errors.New("PD password file is empty") + } + credential = base64.StdEncoding.EncodeToString([]byte("vermeer:" + secret)) + } md := metadata.New(map[string]string{ - CredentialKey: CredentialValue, + CredentialKey: credential, }) if pa.token != "" { md.Set(TokenKey, pa.token) } // logrus.Infof("send md:%v", md) ctx = metadata.NewOutgoingContext(ctx, md) - return ctx + return ctx, nil } func (pa *PDAuthority) SetToken(md metadata.MD) { diff --git a/vermeer/apps/common/pd_authority_test.go b/vermeer/apps/common/pd_authority_test.go new file mode 100644 index 000000000..c1395d7e8 --- /dev/null +++ b/vermeer/apps/common/pd_authority_test.go @@ -0,0 +1,65 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "context" + "encoding/base64" + "os" + "path/filepath" + "testing" + + "google.golang.org/grpc/metadata" +) + +func TestPDAuthorityPasswordFile(t *testing.T) { + passwordFile := filepath.Join(t.TempDir(), "pd-password") + if err := os.WriteFile(passwordFile, []byte("test:shared-secret\n"), 0600); err != nil { + t.Fatal(err) + } + t.Setenv("VERMEER_PD_PASSWORD_FILE", passwordFile) + authority := &PDAuthority{} + ctx, err := authority.SetAuthority(context.Background()) + if err != nil { + t.Fatal(err) + } + md, _ := metadata.FromOutgoingContext(ctx) + values := md.Get(CredentialKey) + if len(values) != 1 { + t.Fatal("missing PD credential") + } + decoded, err := base64.StdEncoding.DecodeString(values[0]) + if err != nil || string(decoded) != "vermeer:test:shared-secret" { + t.Fatal("PD credential must use the configured shared secret") + } +} + +func TestPDAuthorityRejectsUnavailablePasswordFile(t *testing.T) { + for _, missing := range []bool{true, false} { + file := filepath.Join(t.TempDir(), "password") + if !missing { + if err := os.WriteFile(file, nil, 0600); err != nil { + t.Fatal(err) + } + } + t.Setenv("VERMEER_PD_PASSWORD_FILE", file) + if _, err := (&PDAuthority{}).SetAuthority(context.Background()); err == nil { + t.Fatal("must not fall back to legacy credentials for an invalid configured file") + } + } +} diff --git a/vermeer/apps/graphio/hugegraph.go b/vermeer/apps/graphio/hugegraph.go index f204324e3..fb01fdf81 100644 --- a/vermeer/apps/graphio/hugegraph.go +++ b/vermeer/apps/graphio/hugegraph.go @@ -78,7 +78,11 @@ func (a *HugegraphMaker) MakeTasks(params map[string]string, taskID int32) ([]Lo return nil, err } pdAuthority := common.PDAuthority{} - ctx = pdAuthority.SetAuthority(ctx) + defer pdConn.Close() + ctx, err = pdAuthority.SetAuthority(ctx) + if err != nil { + return nil, err + } var md metadata.MD pdClient := pd.NewPDClient(pdConn) partitionsResp, err := pdClient.QueryPartitions(ctx, @@ -110,7 +114,10 @@ func (a *HugegraphMaker) MakeTasks(params map[string]string, taskID int32) ([]Lo } } var leaderStoreID uint64 - ctx = pdAuthority.SetAuthority(ctx) + ctx, err = pdAuthority.SetAuthority(ctx) + if err != nil { + return nil, err + } var md metadata.MD shardGroup, err := pdClient.GetShardGroup(ctx, &pd.GetShardGroupRequest{GroupId: partition.Id}, grpc.Header(&md)) if err != nil { @@ -129,7 +136,10 @@ func (a *HugegraphMaker) MakeTasks(params map[string]string, taskID int32) ([]Lo //重复leader不再执行获取地址 if _, ok := leaderStore[leaderStoreID]; !ok { //获得新的leader的地址并写入map - ctx = pdAuthority.SetAuthority(ctx) + ctx, err = pdAuthority.SetAuthority(ctx) + if err != nil { + return nil, err + } var md metadata.MD storeResp, err := pdClient.GetStore(ctx, &pd.GetStoreRequest{StoreId: leaderStoreID}, grpc.Header(&md)) if err != nil { @@ -195,11 +205,6 @@ func (a *HugegraphMaker) MakeTasks(params map[string]string, taskID int32) ([]Lo loadParts[i].Params["load.hugestore_batch_timeout"] = params["load.hugestore_batch_timeout"] loadParts[i].Params["load.hugestore_batchsize"] = params["load.hugestore_batchsize"] } - err = pdConn.Close() - if err != nil { - logrus.Errorf("hugegraph-pd close err:%v", err) - return nil, err - } return loadParts, nil } diff --git a/vermeer/apps/structure/graph.go b/vermeer/apps/structure/graph.go index 142ede959..19a19866b 100644 --- a/vermeer/apps/structure/graph.go +++ b/vermeer/apps/structure/graph.go @@ -89,11 +89,11 @@ func (vg *VermeerGraph) MallocData(option GraphDataBackendOption) { vg.BackendOption = option vg.Data.MallocData(option) } -func (vg *VermeerGraph) SetOption(useOutEdges bool, useOutDegree bool, useProperty bool) { +func (vg *VermeerGraph) SetOption(useOutEdges bool, useOutDegree bool, useProperty bool) error { vg.UseOutEdges = useOutEdges vg.UseOutDegree = useOutDegree vg.UseProperty = useProperty - vg.Data.SetOption(GraphDataOption{ + return vg.Data.SetOption(GraphDataOption{ graphName: vg.Name, spaceName: vg.SpaceName, dataDir: vg.DataDir, @@ -340,7 +340,7 @@ func (vg *VermeerGraph) Read(workerName string) error { } data := new(GraphData) data.MallocData(vg.BackendOption) - data.SetOption(GraphDataOption{ + err = data.SetOption(GraphDataOption{ graphName: vg.Name, spaceName: vg.SpaceName, dataDir: vg.DataDir, @@ -348,6 +348,9 @@ func (vg *VermeerGraph) Read(workerName string) error { useOutEdges: vg.UseOutEdges, useOutDegree: vg.UseOutDegree, }) + if err != nil { + return err + } err = data.Load(vg.DataDir) if err != nil { return err diff --git a/vermeer/apps/structure/graph_data.go b/vermeer/apps/structure/graph_data.go index 92a5acbc7..80b5e9e17 100644 --- a/vermeer/apps/structure/graph_data.go +++ b/vermeer/apps/structure/graph_data.go @@ -25,7 +25,7 @@ import ( ) type VertexInterface interface { - Init(dataDir string) + Init(dataDir string) error TotalVertexCount() uint32 GetVertex(vertexID uint32) Vertex GetVertexIndex(vertex string) (uint32, bool) @@ -144,13 +144,16 @@ func (gd *GraphData) MallocData(option GraphDataBackendOption) { gd.InEdgesProperty = &EdgeProperties{} } -func (gd *GraphData) SetOption(option GraphDataOption) { +func (gd *GraphData) SetOption(option GraphDataOption) error { gd.graphName = option.graphName gd.spaceName = option.spaceName if option.firstInit { - gd.Vertex.Init(option.dataDir) + if err := gd.Vertex.Init(option.dataDir); err != nil { + return err + } } gd.Edges.Init(option.useOutEdges, option.useOutDegree) + return nil } //func (gd *GraphData) Name() string { diff --git a/vermeer/apps/structure/vertex.go b/vermeer/apps/structure/vertex.go index 33dbef181..411a65ffc 100644 --- a/vermeer/apps/structure/vertex.go +++ b/vermeer/apps/structure/vertex.go @@ -183,9 +183,10 @@ type VertexMem struct { VertexLongIDMap map[string]uint32 } -func (vm *VertexMem) Init(dataDir string) { +func (vm *VertexMem) Init(dataDir string) error { vm.TotalVertex = make([]Vertex, 0) _ = dataDir + return nil } func (vm *VertexMem) TotalVertexCount() uint32 { @@ -252,11 +253,9 @@ type VertexInDB struct { dataDir string } -func (vm *VertexInDB) Init(dataDir string) { - var err error +func (vm *VertexInDB) Init(dataDir string) error { vm.dataDir = vm.makeDataDir(dataDir) - vm.sliceStores = make([]storage.Store, 1) - vm.sliceStores[0], err = storage.StoreMaker(storage.StoreOption{ + store, err := storage.StoreMaker(storage.StoreOption{ StoreName: storage.StoreTypePebble, Path: path.Join(vm.dataDir, "self_vertex_slice"), Fsync: false, @@ -264,9 +263,10 @@ func (vm *VertexInDB) Init(dataDir string) { UseFilter: false, }) if err != nil { - logrus.Errorf("init vertex store error:%v", err) - return + return fmt.Errorf("initialize vertex store: %w", err) } + vm.sliceStores = []storage.Store{store} + return nil } func (vm *VertexInDB) makeDataDir(dataDir string) string { diff --git a/vermeer/apps/structure/vertex_init_test.go b/vermeer/apps/structure/vertex_init_test.go new file mode 100644 index 000000000..8b60a3327 --- /dev/null +++ b/vermeer/apps/structure/vertex_init_test.go @@ -0,0 +1,74 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +*/ + +package structure + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func blockedVertexDirectory(t *testing.T) string { + t.Helper() + file := filepath.Join(t.TempDir(), "not-a-directory") + if err := os.WriteFile(file, []byte("fixture"), 0600); err != nil { + t.Fatal(err) + } + return file +} + +func TestVertexStoreInitializationErrorAndRetry(t *testing.T) { + vertex := &VertexInDB{} + err := vertex.Init(blockedVertexDirectory(t)) + if err == nil || !strings.Contains(err.Error(), "initialize vertex store") { + t.Fatalf("expected vertex storage initialization error, got %v", err) + } + // Failed initialization must remain safe to unload before a retry. + vertex.freeMem() + if err := vertex.Init(t.TempDir()); err != nil { + t.Fatal(err) + } + defer vertex.freeMem() + vertex.workers = []*GraphWorker{{IsSelf: true}} + vertex.AppendVertices(Vertex{ID: "retry-vertex"}) + if vertex.TotalVertexCount() != 1 || vertex.GetVertex(0).ID != "retry-vertex" { + t.Fatal("successful retry must store the loaded vertex") + } +} + +func TestGraphPropagatesVertexStoreInitializationError(t *testing.T) { + graph := &VermeerGraph{DataDir: blockedVertexDirectory(t)} + graph.MallocData(GraphDataBackendOption{VertexDataBackend: DataBackendInDB}) + if err := graph.SetOption(true, true, false); err == nil { + t.Fatal("graph initialization must report vertex storage failure") + } + graph.FreeMem() +} + +func TestMemoryVertexInitialization(t *testing.T) { + graph := &VermeerGraph{} + graph.MallocData(GraphDataBackendOption{VertexDataBackend: DataBackendInMem}) + if err := graph.SetOption(true, true, false); err != nil { + t.Fatal(err) + } + graph.Data.Vertex.AppendVertices(Vertex{ID: "memory-vertex"}) + if graph.Data.Vertex.GetVertex(0).ID != "memory-vertex" { + t.Fatal("memory vertex backend must remain usable") + } +} diff --git a/vermeer/apps/worker/load_graph_bl.go b/vermeer/apps/worker/load_graph_bl.go index 36aae49af..8d8fc3d88 100644 --- a/vermeer/apps/worker/load_graph_bl.go +++ b/vermeer/apps/worker/load_graph_bl.go @@ -39,6 +39,7 @@ type LoadGraphBl struct { } func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName string, workers []string, params map[string]string) { + var readiness *sync.WaitGroup defer func() { if r := recover(); r != nil { lb.SetStatusError(taskId, fmt.Sprintf("StartLoadGraph panic recover panic:%v, stack message: %s", r, @@ -46,6 +47,9 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName logrus.Errorf("StartLoadGraph panic recover taskID:%v, panic:%v, stack message: %s", taskId, r, common.GetCurrentGoroutineStack()) } + if readiness != nil { + readiness.Done() + } }() graph := GraphMgr.CreateGraph(spaceName, graphName) @@ -82,6 +86,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName loadTask := LoadGraphMgr.InstallTask(task) // recv vertex wait until init done loadTask.LoadWg.Add(1) + readiness = loadTask.LoadWg ctx := context.Background() parallel := options.GetInt(params, "load.parallel") if parallel <= 0 { @@ -95,6 +100,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName if err != nil { logrus.Errorf("set data dir error: %s", err) lb.SetStatusError(taskId, fmt.Sprintf("set data dir error: %s", err)) + return } // remove history data graph.Remove() @@ -130,12 +136,14 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName graph.Data.VertexPropertySchema.Init(options.GetMapString(params, "load.vertex_property")) graph.Data.InEdgesPropertySchema.Init(options.GetMapString(params, "load.edge_property")) } - graph.SetOption(useOutEdges, useOutDegree, useProperty) + if err := graph.SetOption(useOutEdges, useOutDegree, useProperty); err != nil { + lb.SetStatusError(taskId, fmt.Sprintf("initialize graph data: %s", err)) + return + } if graph.UseProperty { graph.Data.VertexProperty.Init(graph.Data.VertexPropertySchema) } - loadTask.LoadWg.Done() for i := 0; i < parallel; i++ { valueCtx := context.WithValue(ctx, "worker_id", i) go lb.RunLoadVertex(valueCtx, loadTask) @@ -385,6 +393,9 @@ func (lb *LoadGraphBl) GatherVertex(taskID int32, workerName string, end bool, e //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) loadTask.LoadWg.Wait() + if loadTask.Task.State == structure.TaskStateError { + return + } loadTask.RecvWg.Add(1) //gw := graph.GetGraphWorker(workerName) @@ -865,8 +876,11 @@ func (lb *LoadGraphBl) RecvVertex(taskId int32, worker string, count int32, end if !lb.CheckAction(loadTask) { return } - loadTask.RecvWg.Add(1) loadTask.LoadWg.Wait() + if loadTask.Task.State == structure.TaskStateError { + return + } + loadTask.RecvWg.Add(1) //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) atomic.AddUint32(&graph.Data.VertexCount, uint32(count)) @@ -967,6 +981,9 @@ func (lb *LoadGraphBl) RecvEdge(taskId int32, worker string, count int32, end bo //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) loadTask.LoadWg.Wait() + if loadTask.Task.State == structure.TaskStateError { + return + } loadTask.RecvWg.Add(1) logrus.Debugf("recv edge count: %d, end: %v,endNum:%v, worker: %s", count, end, endNum, worker) From df6f0d28fb61ad55f6f479c0336257b68c5633c4 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 12:46:23 +0000 Subject: [PATCH 02/19] fix(ci): load Vermeer image before checking release metadata --- .github/workflows/vermeer-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index f707498f6..d6241d992 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -103,6 +103,7 @@ jobs: with: context: vermeer push: false + load: true tags: vermeer:ci-check cache-from: type=gha cache-to: type=gha,mode=max From 051c6acc5679db6942ca8142754fc048d6d9d127 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 12:55:18 +0000 Subject: [PATCH 03/19] fix(vermeer): read Schema service credentials outside task parameters --- .github/workflows/vermeer-ci.yml | 4 +- vermeer/README.md | 12 ++ vermeer/apps/common/hugegraph_credentials.go | 50 ++++++++ .../apps/common/hugegraph_credentials_test.go | 97 ++++++++++++++ vermeer/apps/structure/property.go | 4 + .../structure/property_credentials_test.go | 118 ++++++++++++++++++ 6 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 vermeer/apps/common/hugegraph_credentials.go create mode 100644 vermeer/apps/common/hugegraph_credentials_test.go create mode 100644 vermeer/apps/structure/property_credentials_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index d6241d992..b9ba245c5 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -72,10 +72,10 @@ jobs: - name: Generate embedded assets run: cd asset && go generate - - name: Test PD credentials and graph initialization failures + - name: Test deployment credentials and graph initialization failures run: | go test -race ./apps/common - go test -race ./apps/structure -run 'Test(VertexStoreInitializationErrorAndRetry|GraphPropagatesVertexStoreInitializationError|MemoryVertexInitialization)$' + go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' - name: Build run: CGO_ENABLED=0 go build -o vermeer diff --git a/vermeer/README.md b/vermeer/README.md index 35daa24a4..69a28d67a 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -278,6 +278,18 @@ is sent as the internal `vermeer` credential and is never added to task paramete When no file is configured, the legacy credential remains available for older PD deployments; it does not authenticate to a modern PD with a different shared secret. +For authenticated HugeGraph Schema access, configure the Worker with +`hugegraph_credentials_file=/path/to/hugegraph-credentials.json`, or set +`VERMEER_HUGEGRAPH_CREDENTIALS_FILE` (which takes precedence). The protected file +(mode `0600`) contains `{"username":"schema-reader","password":"..."}` and must +belong to a Server account allowed to read the loaded graphs' Schema. Hubble's +direct adapter never forwards the user's login password. Worker Schema discovery +and reads use these deployment credentials without adding them to persisted task +parameters. A configured missing, unreadable, empty or invalid file fails the +load; it never falls back to task credentials. Without a file, the existing +`load.hugegraph_username` / `load.hugegraph_password` behavior is retained. This +setting does not configure the separate HugeGraph result writer. + ```ini [default] # Worker HTTP listen address diff --git a/vermeer/apps/common/hugegraph_credentials.go b/vermeer/apps/common/hugegraph_credentials.go new file mode 100644 index 000000000..7241b3126 --- /dev/null +++ b/vermeer/apps/common/hugegraph_credentials.go @@ -0,0 +1,50 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "encoding/json" + "errors" + "fmt" + "os" +) + +// ResolveHugegraphCredentials keeps deployment credentials out of persisted task parameters. +// Without a configured file, legacy callers retain their task-provided credentials. +func ResolveHugegraphCredentials(username, password string) (string, string, error) { + file := os.Getenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE") + if file == "" { + file, _ = GetConfigDefault("hugegraph_credentials_file", "").(string) + } + if file == "" { + return username, password, nil + } + data, err := os.ReadFile(file) + if err != nil { + return "", "", fmt.Errorf("read HugeGraph credentials file: %w", err) + } + var credentials struct { + Username string `json:"username"` + Password string `json:"password"` + } + if json.Unmarshal(data, &credentials) != nil || credentials.Username == "" || credentials.Password == "" { + // Do not include decoder errors or input bytes in task errors or logs. + return "", "", errors.New("HugeGraph credentials file must contain a nonempty username and password") + } + return credentials.Username, credentials.Password, nil +} diff --git a/vermeer/apps/common/hugegraph_credentials_test.go b/vermeer/apps/common/hugegraph_credentials_test.go new file mode 100644 index 000000000..c5adae484 --- /dev/null +++ b/vermeer/apps/common/hugegraph_credentials_test.go @@ -0,0 +1,97 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestHugegraphCredentialsFilePrecedence(t *testing.T) { + old := GetConfigDefault("hugegraph_credentials_file", "") + t.Cleanup(func() { SetConfig("hugegraph_credentials_file", old) }) + configured := filepath.Join(t.TempDir(), "configured.json") + override := filepath.Join(t.TempDir(), "override.json") + for file, data := range map[string]string{ + configured: `{"username":"config-user","password":"config-secret"}`, + override: `{"username":"environment-user","password":"environment-secret"}`, + } { + if err := os.WriteFile(file, []byte(data), 0600); err != nil { + t.Fatal(err) + } + } + SetConfig("hugegraph_credentials_file", configured) + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", override) + username, password, err := ResolveHugegraphCredentials("task-user", "task-secret") + if err != nil || username != "environment-user" || password != "environment-secret" { + t.Fatal("environment file must override config and task credentials") + } + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", "") + username, password, err = ResolveHugegraphCredentials("task-user", "task-secret") + if err != nil || username != "config-user" || password != "config-secret" { + t.Fatal("configuration file must override task credentials") + } + SetConfig("hugegraph_credentials_file", "") + username, password, err = ResolveHugegraphCredentials("task-user", "task-secret") + if err != nil || username != "task-user" || password != "task-secret" { + t.Fatal("unconfigured deployments must retain legacy task credentials") + } + username, password, err = ResolveHugegraphCredentials("", "") + if err != nil || username != "" || password != "" { + t.Fatal("unconfigured anonymous deployments must remain usable") + } +} + +func TestHugegraphCredentialsFileFailsClosed(t *testing.T) { + old := GetConfigDefault("hugegraph_credentials_file", "") + t.Cleanup(func() { SetConfig("hugegraph_credentials_file", old) }) + fallback := filepath.Join(t.TempDir(), "valid-fallback.json") + if err := os.WriteFile(fallback, []byte(`{"username":"config-user","password":"config-password"}`), 0600); err != nil { + t.Fatal(err) + } + SetConfig("hugegraph_credentials_file", fallback) + for _, test := range []struct{ name, data string }{ + {"missing", ""}, {"empty", ""}, {"malformed", `{"username":"secret-user","password":"secret-password"`}, + {"null", `null`}, {"username-missing", `{"password":"secret-password"}`}, + {"username-empty", `{"username":"","password":"secret-password"}`}, + {"password-missing", `{"username":"secret-user"}`}, + {"password-empty", `{"username":"secret-user","password":""}`}, + {"wrong-type", `{"username":"secret-user","password":123}`}, + } { + t.Run(test.name, func(t *testing.T) { + file := filepath.Join(t.TempDir(), "credentials.json") + if test.name != "missing" { + if err := os.WriteFile(file, []byte(test.data), 0600); err != nil { + t.Fatal(err) + } + } + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", file) + username, password, err := ResolveHugegraphCredentials("task-user", "task-secret") + if err == nil || username != "" || password != "" { + t.Fatal("invalid configured credentials must not fall back to task credentials") + } + for _, secret := range []string{"secret-user", "secret-password", "task-secret"} { + if strings.Contains(err.Error(), secret) { + t.Fatal("credential contents must not appear in errors") + } + } + }) + } +} diff --git a/vermeer/apps/structure/property.go b/vermeer/apps/structure/property.go index 94f6f23d0..e4a9aa31f 100644 --- a/vermeer/apps/structure/property.go +++ b/vermeer/apps/structure/property.go @@ -483,6 +483,10 @@ func GetSchemaFromHugegraph(params map[string]string) (PropertySchema, PropertyS hgName := options.GetString(params, "load.hugegraph_name") username := options.GetString(params, "load.hugegraph_username") password := options.GetString(params, "load.hugegraph_password") + username, password, err := common.ResolveHugegraphCredentials(username, password) + if err != nil { + return PropertySchema{}, PropertySchema{}, err + } useProperty := options.GetInt(params, "load.use_property") == 1 vertexPropIDs := strings.Split(options.GetString(params, "load.vertex_property"), ",") edgePropIDs := strings.Split(options.GetString(params, "load.edge_property"), ",") diff --git a/vermeer/apps/structure/property_credentials_test.go b/vermeer/apps/structure/property_credentials_test.go new file mode 100644 index 000000000..0b0b0a828 --- /dev/null +++ b/vermeer/apps/structure/property_credentials_test.go @@ -0,0 +1,118 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package structure + +import ( + "context" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "sync/atomic" + "testing" + + "google.golang.org/grpc" + "vermeer/apps/options" + pd "vermeer/apps/protos/hugegraph-pd-grpc" +) + +type schemaCredentialPD struct { + pd.UnimplementedPDServer + pd.UnimplementedDiscoveryServiceServer + serverURL string +} + +func (s *schemaCredentialPD) GetMembers(context.Context, *pd.GetMembersRequest) (*pd.GetMembersResponse, error) { + return &pd.GetMembersResponse{}, nil +} + +func (s *schemaCredentialPD) GetNodes(context.Context, *pd.Query) (*pd.NodeInfos, error) { + return &pd.NodeInfos{Info: []*pd.NodeInfo{{ + Address: s.serverURL, Labels: map[string]string{"GRAPHSPACE": "DEFAULT"}, + }}}, nil +} + +func TestHugegraphSchemaDeploymentCredentials(t *testing.T) { + previousOptions := options.AllOptionsMap + options.Init() + t.Cleanup(func() { options.AllOptionsMap = previousOptions }) + var authorized atomic.Int32 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, password, ok := r.BasicAuth() + if !ok || user != "schema-reader" || password != "deployment-secret" { + w.WriteHeader(http.StatusUnauthorized) + return + } + if r.URL.Path != "/graphspaces/DEFAULT/graphs/fresh-graph/schema" || r.URL.Query().Get("format") != "json" { + w.WriteHeader(http.StatusNotFound) + return + } + authorized.Add(1) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"vertexlabels":[],"edgelabels":[],"propertykeys":[]}`)) + })) + defer server.Close() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + discovery := grpc.NewServer() + fixture := &schemaCredentialPD{serverURL: server.URL} + pd.RegisterPDServer(discovery, fixture) + pd.RegisterDiscoveryServiceServer(discovery, fixture) + go func() { _ = discovery.Serve(listener) }() + defer discovery.Stop() + credentials := filepath.Join(t.TempDir(), "credentials.json") + if err := os.WriteFile(credentials, []byte(`{"username":"schema-reader","password":"deployment-secret"}`), 0600); err != nil { + t.Fatal(err) + } + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", credentials) + params := map[string]string{ + "load.hg_pd_peers": `["` + listener.Addr().String() + `"]`, + "load.hugegraph_name": "DEFAULT/fresh-graph/g", "load.use_property": "1", + "load.hugegraph_username": "wrong-task-user", "load.hugegraph_password": "wrong-task-password", + } + original := make(map[string]string) + for k, v := range params { + original[k] = v + } + vertex, edge, err := GetSchemaFromHugegraph(params) + if err != nil { + t.Fatal(err) + } + if vertex.HgPSchema == nil || edge.HgPSchema == nil { + t.Fatal("authenticated Schema response must initialize vertex and edge schemas") + } + if authorized.Load() != 2 { + t.Fatalf("expected authenticated discovery probe and Schema read, got %d", authorized.Load()) + } + if !reflect.DeepEqual(params, original) { + t.Fatal("deployment credentials must not be written into task params") + } +} + +func TestHugegraphSchemaRejectsInvalidCredentialsBeforeDiscovery(t *testing.T) { + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", filepath.Join(t.TempDir(), "missing.json")) + // There is no PD endpoint: credential validation must fail before discovery. + if _, _, err := GetSchemaFromHugegraph(map[string]string{}); err == nil || !strings.Contains(err.Error(), "read HugeGraph credentials file") { + t.Fatalf("expected credential error before discovery, got %v", err) + } +} From 3ad48638bc631650570bff3516cab9233675d9d7 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 13:05:12 +0000 Subject: [PATCH 04/19] fix(vermeer): publish initialization readiness before receiving graph frames --- .github/workflows/vermeer-ci.yml | 1 + vermeer/README.md | 4 + vermeer/apps/graphio/load_graph.go | 6 + vermeer/apps/worker/load_graph_bl.go | 23 +-- .../apps/worker/load_graph_readiness_test.go | 157 ++++++++++++++++++ 5 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 vermeer/apps/worker/load_graph_readiness_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index b9ba245c5..6a3d346c6 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -75,6 +75,7 @@ jobs: - name: Test deployment credentials and graph initialization failures run: | go test -race ./apps/common + go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex)' go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' - name: Build diff --git a/vermeer/README.md b/vermeer/README.md index 69a28d67a..1b4c6ddfa 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -278,6 +278,10 @@ is sent as the internal `vermeer` credential and is never added to task paramete When no file is configured, the legacy credential remains available for older PD deployments; it does not authenticate to a modern PD with a different shared secret. +The current PD gRPC transport is plaintext. Keep these connections on a trusted +private network or loopback; deployments crossing untrusted networks must provide +transport encryption. The password file configures request credentials, not TLS. + For authenticated HugeGraph Schema access, configure the Worker with `hugegraph_credentials_file=/path/to/hugegraph-credentials.json`, or set `VERMEER_HUGEGRAPH_CREDENTIALS_FILE` (which takes precedence). The protected file diff --git a/vermeer/apps/graphio/load_graph.go b/vermeer/apps/graphio/load_graph.go index b09235f21..dd948885b 100644 --- a/vermeer/apps/graphio/load_graph.go +++ b/vermeer/apps/graphio/load_graph.go @@ -172,6 +172,8 @@ type LoadGraphTask struct { SendCount map[string]*int32 RecvCount map[string]*int32 Locker *sync.Mutex + // InitializationFailed is immutable once initialization releases LoadWg. + InitializationFailed bool } func (lt *LoadGraphTask) GetPartition(partIdx int32) LoadPartition { @@ -452,6 +454,7 @@ func (lw *loadGraphWorker) InstallTask(taskInfo *structure.TaskInfo) *LoadGraphT lw.locker.Lock() defer lw.locker.Unlock() if _, ok := lw.loadTasks[taskInfo.ID]; ok { + lw.loadTasks[taskInfo.ID].LoadWg.Add(1) return lw.loadTasks[taskInfo.ID] } loadTask := LoadGraphTask{ @@ -468,6 +471,9 @@ func (lw *loadGraphWorker) InstallTask(taskInfo *structure.TaskInfo) *LoadGraphT loadTask.SendCount[worker.Name] = new(int32) loadTask.RecvCount[worker.Name] = new(int32) } + // Receivers can find this task as soon as it is published. Hold the + // initialization barrier before making it visible to them. + loadTask.LoadWg.Add(1) lw.loadTasks[taskInfo.ID] = &loadTask // TODO: why not a pointer? return &loadTask diff --git a/vermeer/apps/worker/load_graph_bl.go b/vermeer/apps/worker/load_graph_bl.go index 8d8fc3d88..031241f48 100644 --- a/vermeer/apps/worker/load_graph_bl.go +++ b/vermeer/apps/worker/load_graph_bl.go @@ -39,7 +39,8 @@ type LoadGraphBl struct { } func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName string, workers []string, params map[string]string) { - var readiness *sync.WaitGroup + var initializing *graphio.LoadGraphTask + initialized := false defer func() { if r := recover(); r != nil { lb.SetStatusError(taskId, fmt.Sprintf("StartLoadGraph panic recover panic:%v, stack message: %s", r, @@ -47,8 +48,9 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName logrus.Errorf("StartLoadGraph panic recover taskID:%v, panic:%v, stack message: %s", taskId, r, common.GetCurrentGoroutineStack()) } - if readiness != nil { - readiness.Done() + if initializing != nil { + initializing.InitializationFailed = !initialized + initializing.LoadWg.Done() } }() @@ -84,9 +86,8 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName TaskMgr.AddTask(task) logrus.Infof("create load task: %d, graph: %s", taskId, graphName) loadTask := LoadGraphMgr.InstallTask(task) - // recv vertex wait until init done - loadTask.LoadWg.Add(1) - readiness = loadTask.LoadWg + // InstallTask holds the barrier before publishing the task to receivers. + initializing = loadTask ctx := context.Background() parallel := options.GetInt(params, "load.parallel") if parallel <= 0 { @@ -144,6 +145,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName graph.Data.VertexProperty.Init(graph.Data.VertexPropertySchema) } + initialized = true for i := 0; i < parallel; i++ { valueCtx := context.WithValue(ctx, "worker_id", i) go lb.RunLoadVertex(valueCtx, loadTask) @@ -393,7 +395,7 @@ func (lb *LoadGraphBl) GatherVertex(taskID int32, workerName string, end bool, e //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) loadTask.LoadWg.Wait() - if loadTask.Task.State == structure.TaskStateError { + if loadTask.InitializationFailed { return } loadTask.RecvWg.Add(1) @@ -876,11 +878,12 @@ func (lb *LoadGraphBl) RecvVertex(taskId int32, worker string, count int32, end if !lb.CheckAction(loadTask) { return } + loadTask.RecvWg.Add(1) loadTask.LoadWg.Wait() - if loadTask.Task.State == structure.TaskStateError { + if loadTask.InitializationFailed { + loadTask.RecvWg.Done() return } - loadTask.RecvWg.Add(1) //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) atomic.AddUint32(&graph.Data.VertexCount, uint32(count)) @@ -981,7 +984,7 @@ func (lb *LoadGraphBl) RecvEdge(taskId int32, worker string, count int32, end bo //defer lb.CheckAction(loadTask) graph := GraphMgr.GetGraphByName(loadTask.Task.SpaceName, loadTask.Task.GraphName) loadTask.LoadWg.Wait() - if loadTask.Task.State == structure.TaskStateError { + if loadTask.InitializationFailed { return } loadTask.RecvWg.Add(1) diff --git a/vermeer/apps/worker/load_graph_readiness_test.go b/vermeer/apps/worker/load_graph_readiness_test.go new file mode 100644 index 000000000..b4819cb6f --- /dev/null +++ b/vermeer/apps/worker/load_graph_readiness_test.go @@ -0,0 +1,157 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package worker + +import ( + "sync" + "sync/atomic" + "testing" + "time" + + "vermeer/apps/common" + "vermeer/apps/graphio" + "vermeer/apps/structure" + + "github.com/prometheus/client_golang/prometheus" +) + +func receiveFixture(t *testing.T) *graphio.LoadGraphTask { + t.Helper() + GraphMgr.Init() + LoadGraphMgr.Init() + task := LoadGraphMgr.InstallTask(&structure.TaskInfo{ + ID: 5001, SpaceName: "readiness-test", GraphName: "graph", + Workers: []*structure.TaskWorker{{Name: "worker"}}, + }) + t.Cleanup(func() { GraphMgr.Init(); LoadGraphMgr.DeleteTask(task.Task.ID) }) + return task +} + +func TestLoadReceiversUseInitializationResult(t *testing.T) { + task := receiveFixture(t) + task.InitializationFailed = true + task.LoadWg.Done() + // Runtime task errors can change after initialization. Receivers must only + // inspect the immutable result published by the initialization barrier. + stopped, started := make(chan struct{}), make(chan struct{}) + var stop atomic.Bool + go func() { + defer close(stopped) + task.Task.State = structure.TaskStateError + close(started) + for !stop.Load() { + task.Task.State = structure.TaskStateError + } + }() + <-started + t.Cleanup(func() { stop.Store(true); <-stopped }) + receiver := &LoadGraphBl{} + for i := 0; i < 100; i++ { + receiver.RecvVertex(task.Task.ID, "worker", 0, false, 0, nil) + receiver.GatherVertex(task.Task.ID, "worker", false, 0, nil) + receiver.RecvEdge(task.Task.ID, "worker", 0, false, 0, nil) + } + if count := atomic.LoadInt32((*int32)(task.RecvWg)); count != 0 { + t.Fatalf("failed initialization leaked %d registered receivers", count) + } +} + +func TestRecvVertexRegistersBeforeInitialization(t *testing.T) { + for _, failed := range []bool{false, true} { + name := "success" + if failed { + name = "failure" + } + t.Run(name, func(t *testing.T) { + task := receiveFixture(t) + previous := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_receive_graphs"}, nil) + t.Cleanup(func() { common.PrometheusMetrics.GraphCnt = previous }) + graph := GraphMgr.CreateGraph(task.Task.SpaceName, task.Task.GraphName) + graph.MallocData(structure.GraphDataBackendOption{VertexDataBackend: structure.DataBackendInMem}) + if err := graph.SetOption(false, false, false); err != nil { + t.Fatal(err) + } + if err := GraphMgr.AddGraph(graph); err != nil { + t.Fatal(err) + } + var once sync.Once + release := func() { + once.Do(func() { + task.InitializationFailed = failed + task.LoadWg.Done() + }) + } + received := make(chan struct{}) + vertex := structure.Vertex{ID: "delayed-vertex"} + data := make([]byte, vertex.PredictSize()) + if _, err := vertex.Marshal(data); err != nil { + t.Fatal(err) + } + go func() { + (&LoadGraphBl{}).RecvVertex(task.Task.ID, "worker", 1, false, 0, data) + close(received) + }() + t.Cleanup(func() { + release() + select { + case <-received: + case <-time.After(time.Second): + t.Error("receiver did not leave initialization wait") + } + }) + deadline := time.Now().Add(time.Second) + for atomic.LoadInt32((*int32)(task.RecvWg)) == 0 && time.Now().Before(deadline) { + time.Sleep(time.Millisecond) + } + if count := atomic.LoadInt32((*int32)(task.RecvWg)); count != 1 { + t.Fatalf("receiver must register before waiting for initialization, got %d", count) + } + completion := make(chan struct{}) + go func() { task.RecvWg.Wait(); close(completion) }() + // This is the same completion wait used by an ending vertex frame. + select { + case <-completion: + t.Fatal("completion advanced past an initialization-blocked receiver") + case <-time.After(20 * time.Millisecond): + } + release() + select { + case <-received: + case <-time.After(time.Second): + t.Fatal("receiver did not complete") + } + select { + case <-completion: + case <-time.After(time.Second): + t.Fatal("completion waiter leaked after receiver exit") + } + if atomic.LoadInt32((*int32)(task.RecvWg)) != 0 { + t.Fatal("unbalanced receiver accounting") + } + if failed { + if graph.Data.VertexCount != 0 { + t.Fatal("failed initialization must not accept vertices") + } + } else if graph.Data.VertexCount != 1 || graph.Data.Vertex.GetVertex(0).ID != vertex.ID { + t.Fatal("successful initialization must process the registered vertex") + } + }) + } +} From 16afc7fa910499e286eeb19bde6c462b4b25ae41 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 13:47:29 +0000 Subject: [PATCH 05/19] feat(vermeer): expose worker readiness for the authenticated space --- .github/workflows/vermeer-ci.yml | 1 + vermeer/README.md | 7 + vermeer/apps/master/bl/grpc_handlers.go | 5 +- .../services/http_worker_availability.go | 38 ++++++ .../services/http_worker_availability_test.go | 60 ++++++++ vermeer/apps/master/services/router.go | 3 + .../workers/worker_availability_test.go | 128 ++++++++++++++++++ vermeer/apps/master/workers/worker_manager.go | 27 ++++ 8 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 vermeer/apps/master/services/http_worker_availability.go create mode 100644 vermeer/apps/master/services/http_worker_availability_test.go create mode 100644 vermeer/apps/master/workers/worker_availability_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index 6a3d346c6..76268a8db 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -75,6 +75,7 @@ jobs: - name: Test deployment credentials and graph initialization failures run: | go test -race ./apps/common + go test -race ./apps/master/workers ./apps/master/services -run 'TestWorkerAvailability' go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex)' go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' diff --git a/vermeer/README.md b/vermeer/README.md index 1b4c6ddfa..5883ed7bf 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -352,8 +352,15 @@ Vermeer exposes a REST API on port `6688` (configurable in `master.ini`). | `/api/v1/compute` | POST | Execute algorithm on loaded graph | | `/api/v1/tasks/{task_id}` | GET | Get task status and results | | `/api/v1/workers` | GET | List connected workers | +| `/api/v1/workers/availability` | GET | Read worker readiness for the authenticated space | | `/ui/` | GET | Web UI dashboard | +Worker availability uses the normal space credential and returns only +`{"available":true|false}` for that authenticated space's allocations or the +common pool. It accepts no caller-selected space and does not allocate workers +or require an administrator token. Global worker registration alone does not +establish readiness for a particular space. + ### Example: Run PageRank ```bash diff --git a/vermeer/apps/master/bl/grpc_handlers.go b/vermeer/apps/master/bl/grpc_handlers.go index 2734c063f..7296c679e 100644 --- a/vermeer/apps/master/bl/grpc_handlers.go +++ b/vermeer/apps/master/bl/grpc_handlers.go @@ -136,17 +136,18 @@ func (h *ServerHandler) SayHelloMaster(ctx context.Context, req *pb.HelloMasterR grpc.MaxCallSendMsgSize(1*1024*1024*1024), grpc.MaxCallRecvMsgSize(1*1024*1024*1024)) - workerClient.Connection, err = grpc.Dial( + connection, err := grpc.Dial( ip+":"+port, grpc.WithTransportCredentials(insecure.NewCredentials()), dialOptions, grpc.WithBlock(), grpc.WithIdleTimeout(0)) if err != nil { logrus.Errorf("connect worker error: %s", err) return &resp, err } + workerClient.Session = pb.NewWorkerClient(connection) + workerMgr.SetWorkerConnection(workerClient, connection) spaces := structure.SpaceManager.GetAllSpace() for _, space := range spaces { resp.Spaces = append(resp.Spaces, space.Name()) } - workerClient.Session = pb.NewWorkerClient(workerClient.Connection) initWorkerClient(workerClient) diff --git a/vermeer/apps/master/services/http_worker_availability.go b/vermeer/apps/master/services/http_worker_availability.go new file mode 100644 index 000000000..695ba29d0 --- /dev/null +++ b/vermeer/apps/master/services/http_worker_availability.go @@ -0,0 +1,38 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +*/ + +package services + +import ( + "net/http" + "vermeer/apps/common" + + "github.com/gin-gonic/gin" +) + +// WorkerAvailabilityHandler reports readiness only for the authenticated space. +type WorkerAvailabilityHandler struct { + common.SenHandler +} + +func (wh *WorkerAvailabilityHandler) GET(ctx *gin.Context) { + writeWorkerAvailability(ctx, WorkerMgr.HasAvailableWorkers) +} + +func writeWorkerAvailability(ctx *gin.Context, available func(string) bool) { + ctx.JSON(http.StatusOK, gin.H{"available": available(credential(ctx).Space())}) +} diff --git a/vermeer/apps/master/services/http_worker_availability_test.go b/vermeer/apps/master/services/http_worker_availability_test.go new file mode 100644 index 000000000..c5eace164 --- /dev/null +++ b/vermeer/apps/master/services/http_worker_availability_test.go @@ -0,0 +1,60 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +*/ + +package services + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "vermeer/apps/structure" + + "github.com/gin-gonic/gin" +) + +func TestWorkerAvailabilityUsesAuthenticatedSpace(t *testing.T) { + credential, err := structure.NewCredential("space-user", "own-space") + if err != nil { + t.Fatal(err) + } + router := gin.New() + var checked string + router.GET("/workers/availability", func(ctx *gin.Context) { + ctx.Set(structure.CredentialKey, credential) + writeWorkerAvailability(ctx, func(space string) bool { + checked = space + return space == "own-space" + }) + }) + response := httptest.NewRecorder() + router.ServeHTTP(response, httptest.NewRequest(http.MethodGet, + "/workers/availability?space=another-space", nil)) + if checked != "own-space" { + t.Fatalf("checked untrusted space %q", checked) + } + if response.Code != http.StatusOK { + t.Fatalf("status = %d", response.Code) + } + var body map[string]any + if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + if len(body) != 1 || body["available"] != true { + t.Fatalf("response must contain only readiness: %v", body) + } +} diff --git a/vermeer/apps/master/services/router.go b/vermeer/apps/master/services/router.go index ac01e8340..2c3887ec5 100644 --- a/vermeer/apps/master/services/router.go +++ b/vermeer/apps/master/services/router.go @@ -63,6 +63,9 @@ func SetRouters(sen *common.Sentinel, authFilters ...gin.HandlerFunc) { regVerAPI(sen, 1, "/workers", map[string]common.BaseHandler{ "": &WorkersHandler{}, }) + regVerAPI(sen, 1, "/workers", map[string]common.BaseHandler{ + "/availability": &WorkerAvailabilityHandler{}, + }, authFilters...) // /master regVerAPI(sen, 1, "/master", map[string]common.BaseHandler{ diff --git a/vermeer/apps/master/workers/worker_availability_test.go b/vermeer/apps/master/workers/worker_availability_test.go new file mode 100644 index 000000000..31ceaa7b5 --- /dev/null +++ b/vermeer/apps/master/workers/worker_availability_test.go @@ -0,0 +1,128 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +*/ + +package workers + +import ( + "context" + "maps" + "net" + "sync" + "testing" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +func readyWorkerConnection(t *testing.T) *grpc.ClientConn { + t.Helper() + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer() + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { server.Stop(); _ = listener.Close() }) + connection, err := grpc.NewClient("passthrough:///availability-test", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() })) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = connection.Close() }) + connection.Connect() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + for { + state := connection.GetState() + if state == connectivity.Ready { + return connection + } + if !connection.WaitForStateChange(ctx, state) { + t.Fatal("worker connection did not become ready") + } + } +} + +func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { + connection := readyWorkerConnection(t) + cases := []struct { + name string + group string + mappings map[string]string + available bool + }{ + {"unallocated named group", "default", nil, false}, + {"another space", "default", map[string]string{"other": "default"}, false}, + {"space allocation", "default", map[string]string{"current": "default"}, true}, + {"graph allocation", "default", map[string]string{"current graph": "default"}, true}, + {"other graph", "default", map[string]string{"current-other graph": "default"}, false}, + {"common worker", "$", nil, true}, + {"ungrouped worker", "", nil, true}, + } + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + manager := workerManager{ + workersByName: map[string]*WorkerClient{ + "worker": {Name: "worker", Group: test.group, Connection: connection}, + }, + groupMapper: test.mappings, + } + before := maps.Clone(manager.groupMapper) + if got := manager.HasAvailableWorkers("current"); got != test.available { + t.Fatalf("availability = %v, want %v", got, test.available) + } + if !maps.Equal(manager.groupMapper, before) { + t.Fatal("readiness changed allocations") + } + }) + } +} + +func TestWorkerAvailabilityHandlesRegistrationAndDisconnect(t *testing.T) { + connection := readyWorkerConnection(t) + manager := workerManager{workersByName: map[string]*WorkerClient{}, groupMapper: map[string]string{}} + if manager.HasAvailableWorkers("current") { + t.Fatal("missing worker must not be ready") + } + manager.workersByName["worker"] = &WorkerClient{Group: "$"} + if manager.HasAvailableWorkers("current") { + t.Fatal("unconnected worker must not be ready") + } + worker := manager.workersByName["worker"] + manager.SetWorkerConnection(worker, connection) + if !manager.HasAvailableWorkers("current") { + t.Fatal("registered ready worker must be available") + } + var wait sync.WaitGroup + wait.Add(1) + go func() { + defer wait.Done() + for i := 0; i < 1000; i++ { + manager.HasAvailableWorkers("current") + } + }() + for i := 0; i < 1000; i++ { + manager.SetWorkerConnection(worker, nil) + manager.SetWorkerConnection(worker, connection) + } + _ = connection.Close() + wait.Wait() + if manager.HasAvailableWorkers("current") { + t.Fatal("disconnected worker must not be ready") + } +} diff --git a/vermeer/apps/master/workers/worker_manager.go b/vermeer/apps/master/workers/worker_manager.go index f2ea2fb62..354d3ea27 100644 --- a/vermeer/apps/master/workers/worker_manager.go +++ b/vermeer/apps/master/workers/worker_manager.go @@ -233,6 +233,33 @@ func (wm *workerManager) CheckWorkerAlive(name string) bool { return true } +// SetWorkerConnection publishes a completed registration dial to readiness readers. +func (wm *workerManager) SetWorkerConnection(worker *WorkerClient, connection *grpc.ClientConn) { + defer wm.Unlock(wm.Lock()) + worker.Connection = connection +} + +// HasAvailableWorkers reports online candidates already assigned to this space +// or one of its graphs, or available to all spaces. It never allocates workers. +func (wm *workerManager) HasAvailableWorkers(space string) bool { + defer wm.Unlock(wm.Lock()) + groups := make(map[string]bool) + for owner, group := range wm.groupMapper { + if owner == space || strings.HasPrefix(owner, space+" ") { + groups[group] = true + } + } + for _, worker := range wm.workersByName { + if worker.Group != "" && worker.Group != "$" && !groups[worker.Group] { + continue + } + if worker.Connection != nil && worker.Connection.GetState() == connectivity.Ready { + return true + } + } + return false +} + func (wm *workerManager) SetWorkerGroup(workerName, workerGroup string) error { if workerName == "" { return fmt.Errorf("the argument 'workerName' is invalid") From 36598e9b07bc2d4fe067b2c7fd5ca7d867a34c38 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 13:53:16 +0000 Subject: [PATCH 06/19] fix(vermeer): retain the initialization barrier through task cleanup --- vermeer/apps/graphio/load_graph.go | 2 + vermeer/apps/worker/load_graph_bl.go | 4 +- .../apps/worker/load_graph_readiness_test.go | 42 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/vermeer/apps/graphio/load_graph.go b/vermeer/apps/graphio/load_graph.go index dd948885b..b52ee21ff 100644 --- a/vermeer/apps/graphio/load_graph.go +++ b/vermeer/apps/graphio/load_graph.go @@ -173,6 +173,8 @@ type LoadGraphTask struct { RecvCount map[string]*int32 Locker *sync.Mutex // InitializationFailed is immutable once initialization releases LoadWg. + // Later-phase receivers keep a defensive guard; current load-step ordering + // prevents scatter/edge messages after initialization failure. InitializationFailed bool } diff --git a/vermeer/apps/worker/load_graph_bl.go b/vermeer/apps/worker/load_graph_bl.go index 031241f48..021679ee2 100644 --- a/vermeer/apps/worker/load_graph_bl.go +++ b/vermeer/apps/worker/load_graph_bl.go @@ -40,6 +40,7 @@ type LoadGraphBl struct { func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName string, workers []string, params map[string]string) { var initializing *graphio.LoadGraphTask + var initializationWg *sync.WaitGroup initialized := false defer func() { if r := recover(); r != nil { @@ -50,7 +51,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName } if initializing != nil { initializing.InitializationFailed = !initialized - initializing.LoadWg.Done() + initializationWg.Done() } }() @@ -88,6 +89,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName loadTask := LoadGraphMgr.InstallTask(task) // InstallTask holds the barrier before publishing the task to receivers. initializing = loadTask + initializationWg = loadTask.LoadWg ctx := context.Background() parallel := options.GetInt(params, "load.parallel") if parallel <= 0 { diff --git a/vermeer/apps/worker/load_graph_readiness_test.go b/vermeer/apps/worker/load_graph_readiness_test.go index b4819cb6f..8d395bc0e 100644 --- a/vermeer/apps/worker/load_graph_readiness_test.go +++ b/vermeer/apps/worker/load_graph_readiness_test.go @@ -25,6 +25,7 @@ import ( "vermeer/apps/common" "vermeer/apps/graphio" + "vermeer/apps/options" "vermeer/apps/structure" "github.com/prometheus/client_golang/prometheus" @@ -71,6 +72,47 @@ func TestLoadReceiversUseInitializationResult(t *testing.T) { } } +func TestLoadReceiversBarrierSurvivesTaskCleanup(t *testing.T) { + GraphMgr.Init() + LoadGraphMgr.Init() + TaskMgr.Init() + previousOptions := options.AllOptionsMap + options.Init() + previousGraphs := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_cleanup_graphs"}, nil) + t.Cleanup(func() { + GraphMgr.Init() + LoadGraphMgr.DeleteTask(-5002) + TaskMgr.Init() + options.AllOptionsMap = previousOptions + common.PrometheusMetrics.GraphCnt = previousGraphs + }) + var task *graphio.LoadGraphTask + var barrier *sync.WaitGroup + options.AllOptionsMap["load.type"] = &options.Option{ + Convert: func(string) (interface{}, error) { + task = LoadGraphMgr.GetLoadTask(-5002) + barrier = task.LoadWg + // Simulate cancellation cleanup while initialization is still active. + task.FreeMemory() + panic("initialization failed after cleanup") + }, + } + (&LoadGraphBl{}).StartLoadGraph("readiness-test", -5002, "graph", nil, + map[string]string{"load.type": "cleanup-test"}) + if task == nil || barrier == nil || task.LoadWg != nil || !task.InitializationFailed { + t.Fatal("initialization must publish failure after task cleanup") + } + released := make(chan struct{}) + go func() { barrier.Wait(); close(released) }() + select { + case <-released: + case <-time.After(time.Second): + t.Fatal("cleanup left the original initialization barrier blocked") + } +} + func TestRecvVertexRegistersBeforeInitialization(t *testing.T) { for _, failed := range []bool{false, true} { name := "success" From 25cb251030c31a033224b055af0ea53194e55d45 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 14:01:20 +0000 Subject: [PATCH 07/19] fix(vermeer): release initialization receivers before reporting errors --- vermeer/apps/worker/load_graph_bl.go | 18 +++---- .../apps/worker/load_graph_readiness_test.go | 47 +++++++++++++++++-- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/vermeer/apps/worker/load_graph_bl.go b/vermeer/apps/worker/load_graph_bl.go index 021679ee2..9f0dfade4 100644 --- a/vermeer/apps/worker/load_graph_bl.go +++ b/vermeer/apps/worker/load_graph_bl.go @@ -41,18 +41,22 @@ type LoadGraphBl struct { func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName string, workers []string, params map[string]string) { var initializing *graphio.LoadGraphTask var initializationWg *sync.WaitGroup + var initializationError string initialized := false defer func() { if r := recover(); r != nil { - lb.SetStatusError(taskId, fmt.Sprintf("StartLoadGraph panic recover panic:%v, stack message: %s", r, - common.GetCurrentGoroutineStack())) - logrus.Errorf("StartLoadGraph panic recover taskID:%v, panic:%v, stack message: %s", taskId, r, + initializationError = fmt.Sprintf("StartLoadGraph panic recover panic:%v, stack message: %s", r, common.GetCurrentGoroutineStack()) } + // Release local receivers before status reporting, which may block on Master. if initializing != nil { initializing.InitializationFailed = !initialized initializationWg.Done() } + if initializationError != "" { + logrus.Errorf("initialize graph task %d failed: %s", taskId, initializationError) + lb.SetStatusError(taskId, initializationError) + } }() graph := GraphMgr.CreateGraph(spaceName, graphName) @@ -101,8 +105,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName _, err = graph.SetDataDir(spaceName, graphName, ServiceWorker.WorkerName) if err != nil { - logrus.Errorf("set data dir error: %s", err) - lb.SetStatusError(taskId, fmt.Sprintf("set data dir error: %s", err)) + initializationError = fmt.Sprintf("set data dir error: %s", err) return } // remove history data @@ -129,8 +132,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName //logrus.Infof(" hugegraph vertex schema %v", graph.Data.VertexPropertySchema) //logrus.Infof(" hugegraph edge schema %v", graph.Data.InEdgesPropertySchema) if err != nil { - lb.SetStatusError(taskId, fmt.Sprintf("load schema from hugegraph error:%v", err)) - logrus.Errorf("load schema from hugegraph error:%v", err) + initializationError = fmt.Sprintf("load schema from hugegraph error:%v", err) return } @@ -140,7 +142,7 @@ func (lb *LoadGraphBl) StartLoadGraph(spaceName string, taskId int32, graphName graph.Data.InEdgesPropertySchema.Init(options.GetMapString(params, "load.edge_property")) } if err := graph.SetOption(useOutEdges, useOutDegree, useProperty); err != nil { - lb.SetStatusError(taskId, fmt.Sprintf("initialize graph data: %s", err)) + initializationError = fmt.Sprintf("initialize graph data: %s", err) return } if graph.UseProperty { diff --git a/vermeer/apps/worker/load_graph_readiness_test.go b/vermeer/apps/worker/load_graph_readiness_test.go index 8d395bc0e..36e7a68ce 100644 --- a/vermeer/apps/worker/load_graph_readiness_test.go +++ b/vermeer/apps/worker/load_graph_readiness_test.go @@ -18,6 +18,7 @@ limitations under the License. package worker import ( + "context" "sync" "sync/atomic" "testing" @@ -26,11 +27,26 @@ import ( "vermeer/apps/common" "vermeer/apps/graphio" "vermeer/apps/options" + pb "vermeer/apps/protos" "vermeer/apps/structure" "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" ) +type blockingLoadStatusClient struct { + pb.MasterClient + started chan struct{} + release <-chan struct{} +} + +func (client *blockingLoadStatusClient) LoadTaskStatus(context.Context, *pb.LoadTaskStatusReq, + ...grpc.CallOption) (*pb.LoadTaskStatusResp, error) { + close(client.started) + <-client.release + return &pb.LoadTaskStatusResp{}, nil +} + func receiveFixture(t *testing.T) *graphio.LoadGraphTask { t.Helper() GraphMgr.Init() @@ -99,8 +115,33 @@ func TestLoadReceiversBarrierSurvivesTaskCleanup(t *testing.T) { panic("initialization failed after cleanup") }, } - (&LoadGraphBl{}).StartLoadGraph("readiness-test", -5002, "graph", nil, - map[string]string{"load.type": "cleanup-test"}) + reported, release, returned := make(chan struct{}), make(chan struct{}), make(chan struct{}) + previousMaster := ServiceWorker.MasterClient + ServiceWorker.MasterClient = &blockingLoadStatusClient{started: reported, release: release} + t.Cleanup(func() { + close(release) + select { + case <-returned: + case <-time.After(time.Second): + t.Error("initialization did not return after status report resumed") + } + ServiceWorker.MasterClient = previousMaster + }) + go func() { + defer close(returned) + (&LoadGraphBl{}).StartLoadGraph("readiness-test", -5002, "graph", nil, + map[string]string{"load.type": "cleanup-test"}) + }() + select { + case <-reported: + case <-time.After(time.Second): + t.Fatal("initialization did not report its failure") + } + select { + case <-returned: + t.Fatal("status report must still be blocked") + default: + } if task == nil || barrier == nil || task.LoadWg != nil || !task.InitializationFailed { t.Fatal("initialization must publish failure after task cleanup") } @@ -109,7 +150,7 @@ func TestLoadReceiversBarrierSurvivesTaskCleanup(t *testing.T) { select { case <-released: case <-time.After(time.Second): - t.Fatal("cleanup left the original initialization barrier blocked") + t.Fatal("blocked status report left the original initialization barrier blocked") } } From 554daabae5b5ac2cadd3f23d464f54ec96d05eeb Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 14:08:06 +0000 Subject: [PATCH 08/19] fix(vermeer): match worker readiness to allocation precedence --- .../services/http_worker_availability.go | 17 +++++- .../services/http_worker_availability_test.go | 16 +++++- .../workers/worker_availability_test.go | 56 ++++++++++++++----- vermeer/apps/master/workers/worker_manager.go | 31 +++++++--- 4 files changed, 94 insertions(+), 26 deletions(-) diff --git a/vermeer/apps/master/services/http_worker_availability.go b/vermeer/apps/master/services/http_worker_availability.go index 695ba29d0..bdccd7342 100644 --- a/vermeer/apps/master/services/http_worker_availability.go +++ b/vermeer/apps/master/services/http_worker_availability.go @@ -33,6 +33,19 @@ func (wh *WorkerAvailabilityHandler) GET(ctx *gin.Context) { writeWorkerAvailability(ctx, WorkerMgr.HasAvailableWorkers) } -func writeWorkerAvailability(ctx *gin.Context, available func(string) bool) { - ctx.JSON(http.StatusOK, gin.H{"available": available(credential(ctx).Space())}) +func writeWorkerAvailability(ctx *gin.Context, available func(string, []string) bool) { + space := credential(ctx).Space() + ctx.JSON(http.StatusOK, gin.H{"available": available(space, availabilityGraphNames(space))}) +} + +func availabilityGraphNames(space string) []string { + // Match AddGraph's publication lock before GetGraphs takes the space lock. + // Names are immutable; do not read the graph's changing Workers slice. + defer GraphMgr.Unlock(GraphMgr.Lock()) + graphs := GraphMgr.GetGraphs(space) + names := make([]string, 0, len(graphs)) + for _, graph := range graphs { + names = append(names, graph.Name) + } + return names } diff --git a/vermeer/apps/master/services/http_worker_availability_test.go b/vermeer/apps/master/services/http_worker_availability_test.go index c5eace164..0c73b349e 100644 --- a/vermeer/apps/master/services/http_worker_availability_test.go +++ b/vermeer/apps/master/services/http_worker_availability_test.go @@ -22,9 +22,11 @@ import ( "net/http" "net/http/httptest" "testing" + "vermeer/apps/common" "vermeer/apps/structure" "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" ) func TestWorkerAvailabilityUsesAuthenticatedSpace(t *testing.T) { @@ -32,13 +34,23 @@ func TestWorkerAvailabilityUsesAuthenticatedSpace(t *testing.T) { if err != nil { t.Fatal(err) } + previousGraphs := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_availability_graphs"}, nil) + GraphMgr.Init() + t.Cleanup(func() { GraphMgr.Init(); common.PrometheusMetrics.GraphCnt = previousGraphs }) + for _, space := range []string{"own-space", "another-space"} { + if err := GraphMgr.AddGraph(GraphMgr.CreateGraph(space, space+"-graph")); err != nil { + t.Fatal(err) + } + } router := gin.New() var checked string router.GET("/workers/availability", func(ctx *gin.Context) { ctx.Set(structure.CredentialKey, credential) - writeWorkerAvailability(ctx, func(space string) bool { + writeWorkerAvailability(ctx, func(space string, graphs []string) bool { checked = space - return space == "own-space" + return space == "own-space" && len(graphs) == 1 && graphs[0] == "own-space-graph" }) }) response := httptest.NewRecorder() diff --git a/vermeer/apps/master/workers/worker_availability_test.go b/vermeer/apps/master/workers/worker_availability_test.go index 31ceaa7b5..5a6ca594e 100644 --- a/vermeer/apps/master/workers/worker_availability_test.go +++ b/vermeer/apps/master/workers/worker_availability_test.go @@ -64,15 +64,16 @@ func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { name string group string mappings map[string]string + graphs []string available bool }{ - {"unallocated named group", "default", nil, false}, - {"another space", "default", map[string]string{"other": "default"}, false}, - {"space allocation", "default", map[string]string{"current": "default"}, true}, - {"graph allocation", "default", map[string]string{"current graph": "default"}, true}, - {"other graph", "default", map[string]string{"current-other graph": "default"}, false}, - {"common worker", "$", nil, true}, - {"ungrouped worker", "", nil, true}, + {"unallocated named group", "default", nil, nil, false}, + {"another space", "default", map[string]string{"other": "default"}, nil, false}, + {"space allocation", "default", map[string]string{"current": "default"}, nil, true}, + {"graph allocation", "default", map[string]string{"current graph": "default"}, []string{"graph"}, true}, + {"other graph", "default", map[string]string{"current-other graph": "default"}, []string{"graph"}, false}, + {"common worker", "$", nil, nil, true}, + {"other space with shared prefix", "default", map[string]string{"current graph": "default"}, nil, false}, } for _, test := range cases { t.Run(test.name, func(t *testing.T) { @@ -83,7 +84,7 @@ func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { groupMapper: test.mappings, } before := maps.Clone(manager.groupMapper) - if got := manager.HasAvailableWorkers("current"); got != test.available { + if got := manager.HasAvailableWorkers("current", test.graphs); got != test.available { t.Fatalf("availability = %v, want %v", got, test.available) } if !maps.Equal(manager.groupMapper, before) { @@ -96,16 +97,16 @@ func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { func TestWorkerAvailabilityHandlesRegistrationAndDisconnect(t *testing.T) { connection := readyWorkerConnection(t) manager := workerManager{workersByName: map[string]*WorkerClient{}, groupMapper: map[string]string{}} - if manager.HasAvailableWorkers("current") { + if manager.HasAvailableWorkers("current", nil) { t.Fatal("missing worker must not be ready") } manager.workersByName["worker"] = &WorkerClient{Group: "$"} - if manager.HasAvailableWorkers("current") { + if manager.HasAvailableWorkers("current", nil) { t.Fatal("unconnected worker must not be ready") } worker := manager.workersByName["worker"] manager.SetWorkerConnection(worker, connection) - if !manager.HasAvailableWorkers("current") { + if !manager.HasAvailableWorkers("current", nil) { t.Fatal("registered ready worker must be available") } var wait sync.WaitGroup @@ -113,7 +114,7 @@ func TestWorkerAvailabilityHandlesRegistrationAndDisconnect(t *testing.T) { go func() { defer wait.Done() for i := 0; i < 1000; i++ { - manager.HasAvailableWorkers("current") + manager.HasAvailableWorkers("current", nil) } }() for i := 0; i < 1000; i++ { @@ -122,7 +123,36 @@ func TestWorkerAvailabilityHandlesRegistrationAndDisconnect(t *testing.T) { } _ = connection.Close() wait.Wait() - if manager.HasAvailableWorkers("current") { + if manager.HasAvailableWorkers("current", nil) { t.Fatal("disconnected worker must not be ready") } } + +func TestWorkerAvailabilityWaitsForSelectedGroup(t *testing.T) { + connection := readyWorkerConnection(t) + manager := workerManager{ + workersByName: map[string]*WorkerClient{ + "first": {Group: "assigned", Connection: connection}, + "second": {Group: "assigned"}, + "common": {Group: "$", Connection: connection}, + }, + groupMapper: map[string]string{"current": "assigned"}, + } + if manager.HasAvailableWorkers("current", nil) { + t.Fatal("common workers cannot override an explicit group with an unready member") + } + manager.SetWorkerConnection(manager.workersByName["second"], connection) + if !manager.HasAvailableWorkers("current", nil) { + t.Fatal("the selected group must become available when all members are ready") + } + delete(manager.workersByName, "first") + delete(manager.workersByName, "second") + if manager.HasAvailableWorkers("current", nil) { + t.Fatal("an empty explicit group cannot fall back to common workers") + } + manager.workersByName["graph-worker"] = &WorkerClient{Group: "graph-group", Connection: connection} + manager.groupMapper[manager.toOwnerKey("current", "graph")] = "graph-group" + if !manager.HasAvailableWorkers("current", []string{"graph"}) { + t.Fatal("an existing graph's explicit ready group remains an available workload") + } +} diff --git a/vermeer/apps/master/workers/worker_manager.go b/vermeer/apps/master/workers/worker_manager.go index 354d3ea27..741736b2c 100644 --- a/vermeer/apps/master/workers/worker_manager.go +++ b/vermeer/apps/master/workers/worker_manager.go @@ -239,21 +239,34 @@ func (wm *workerManager) SetWorkerConnection(worker *WorkerClient, connection *g worker.Connection = connection } -// HasAvailableWorkers reports online candidates already assigned to this space -// or one of its graphs, or available to all spaces. It never allocates workers. -func (wm *workerManager) HasAvailableWorkers(space string) bool { +// HasAvailableWorkers follows existing allocation precedence without allocating. +// Every registered member of a candidate group must have a ready connection. +func (wm *workerManager) HasAvailableWorkers(space string, graphNames []string) bool { defer wm.Unlock(wm.Lock()) groups := make(map[string]bool) - for owner, group := range wm.groupMapper { - if owner == space || strings.HasPrefix(owner, space+" ") { + group, explicit := wm.groupMapper[space] + if !explicit { + group = "$" // AddWorker normalizes ungrouped registered workers to "$". + } + groups[group] = true + for _, graph := range graphNames { + if group, ok := wm.groupMapper[wm.toOwnerKey(space, graph)]; ok { groups[group] = true } } - for _, worker := range wm.workersByName { - if worker.Group != "" && worker.Group != "$" && !groups[worker.Group] { - continue + for group := range groups { + found, ready := false, true + for _, worker := range wm.workersByName { + if worker.Group != group { + continue + } + found = true + if worker.Connection == nil || worker.Connection.GetState() != connectivity.Ready { + ready = false + break + } } - if worker.Connection != nil && worker.Connection.GetState() == connectivity.Ready { + if found && ready { return true } } From 090bf276e2c7860c292f317c3569559034621c21 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 14:46:51 +0000 Subject: [PATCH 09/19] fix(vermeer): wait for task streams before worker readiness --- .github/workflows/vermeer-ci.yml | 1 + vermeer/apps/master/bl/grpc_handlers.go | 9 +- vermeer/apps/master/bl/server_manager.go | 33 ++++- vermeer/apps/master/bl/task_bl.go | 6 +- vermeer/apps/master/bl/task_starter.go | 6 +- .../master/bl/worker_availability_test.go | 121 ++++++++++++++++++ vermeer/apps/master/schedules/broker.go | 19 +-- .../workers/worker_availability_test.go | 61 ++++++++- vermeer/apps/master/workers/worker_manager.go | 52 +++++++- 9 files changed, 274 insertions(+), 34 deletions(-) create mode 100644 vermeer/apps/master/bl/worker_availability_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index 76268a8db..eba9cb77d 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -76,6 +76,7 @@ jobs: run: | go test -race ./apps/common go test -race ./apps/master/workers ./apps/master/services -run 'TestWorkerAvailability' + go test -race ./apps/master/bl -run 'TestWorkerAvailability' go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex)' go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' diff --git a/vermeer/apps/master/bl/grpc_handlers.go b/vermeer/apps/master/bl/grpc_handlers.go index 7296c679e..d2cb3b750 100644 --- a/vermeer/apps/master/bl/grpc_handlers.go +++ b/vermeer/apps/master/bl/grpc_handlers.go @@ -236,11 +236,10 @@ func (h *ServerHandler) SuperStep(req *pb.SuperStepReq, stream pb.Master_SuperSt if err := ServerMgr.PutSuperStepServer(name, stream); err != nil { return err } - - for i := 0; i < 100000000; i++ { - time.Sleep(10 * time.Second) - } - //WorkerMgr.GetWorker(name).SuperStepServer.RecvHandler(name) + worker := workerMgr.GetWorker(name) + workerMgr.SetTaskStream(worker, workers.SuperStepStream, stream.Context()) + defer workerMgr.ClearTaskStream(worker, workers.SuperStepStream, stream.Context()) + <-stream.Context().Done() return nil } diff --git a/vermeer/apps/master/bl/server_manager.go b/vermeer/apps/master/bl/server_manager.go index 6f3467439..7e2e27adf 100644 --- a/vermeer/apps/master/bl/server_manager.go +++ b/vermeer/apps/master/bl/server_manager.go @@ -20,6 +20,7 @@ package bl import ( "errors" "sync" + "vermeer/apps/master/workers" pb "vermeer/apps/protos" ) @@ -42,11 +43,21 @@ func (s *ServerManager) StartingLoadServer(workerName string, stream pb.Master_L } loadServer := &LoadGraphServer{streamServer: stream} + s.locker.Lock() s.loadSevers[workerName] = loadServer + s.locker.Unlock() + worker := workerMgr.GetWorker(workerName) + workerMgr.SetTaskStream(worker, workers.LoadTaskStream, stream.Context()) + defer func() { + workerMgr.ClearTaskStream(worker, workers.LoadTaskStream, stream.Context()) + s.locker.Lock() + defer s.locker.Unlock() + if s.loadSevers[workerName] == loadServer { + delete(s.loadSevers, workerName) + } + }() loadServer.RecvHandler(workerName) - delete(s.loadSevers, workerName) - return nil } @@ -56,11 +67,21 @@ func (s *ServerManager) StartingComputeServer(workerName string, stream pb.Maste } computeServer := &ComputeTaskServer{streamServer: stream} + s.locker.Lock() s.computeSevers[workerName] = computeServer + s.locker.Unlock() + worker := workerMgr.GetWorker(workerName) + workerMgr.SetTaskStream(worker, workers.ComputeTaskStream, stream.Context()) + defer func() { + workerMgr.ClearTaskStream(worker, workers.ComputeTaskStream, stream.Context()) + s.locker.Lock() + defer s.locker.Unlock() + if s.computeSevers[workerName] == computeServer { + delete(s.computeSevers, workerName) + } + }() computeServer.RecvHandler(workerName) - delete(s.computeSevers, workerName) - return nil } func (s *ServerManager) PutSuperStepServer(workerName string, stream pb.Master_SuperStepServer) error { @@ -78,11 +99,15 @@ func (s *ServerManager) PutSuperStepServer(workerName string, stream pb.Master_S return nil } func (s *ServerManager) LoadServer(workerName string) *LoadGraphServer { + s.locker.Lock() + defer s.locker.Unlock() // TODO: handle nil return s.loadSevers[workerName] } func (s *ServerManager) ComputeServer(workerName string) *ComputeTaskServer { + s.locker.Lock() + defer s.locker.Unlock() // TODO: handle nil return s.computeSevers[workerName] } diff --git a/vermeer/apps/master/bl/task_bl.go b/vermeer/apps/master/bl/task_bl.go index 1c7e43c9a..44977823a 100644 --- a/vermeer/apps/master/bl/task_bl.go +++ b/vermeer/apps/master/bl/task_bl.go @@ -344,7 +344,11 @@ func sendLoadingReq(task *structure.TaskInfo, workerClients []*WorkerClient, app } task.Workers = append(task.Workers, &taskWorker) //err := wc.loadServer.SendLoadReq(apply(wc)) - err := ServerMgr.LoadServer(wc.Name).SendLoadReq(apply(wc)) + server := ServerMgr.LoadServer(wc.Name) + if server == nil { + return fmt.Errorf("worker %s load task stream is unavailable", wc.Name) + } + err := server.SendLoadReq(apply(wc)) if err != nil { //task.SetState(structure.TaskStateError) taskMgr.SetError(task, err.Error()) diff --git a/vermeer/apps/master/bl/task_starter.go b/vermeer/apps/master/bl/task_starter.go index 5549baeeb..33ba596be 100644 --- a/vermeer/apps/master/bl/task_starter.go +++ b/vermeer/apps/master/bl/task_starter.go @@ -309,7 +309,11 @@ func StartComputeTask(graph *structure.VermeerGraph, computeTask *compute.Comput } computeTask.Task.SetWorkerState(w.Name, workerState) //err := wc.computeServer.AsyncCompute( - err := ServerMgr.ComputeServer(wc.Name).AsyncCompute( + server := ServerMgr.ComputeServer(wc.Name) + if server == nil { + return fmt.Errorf("worker %s compute task stream is unavailable", wc.Name) + } + err := server.AsyncCompute( computeTask.Algorithm, computeTask.Task.GraphName, computeTask.Task.SpaceName, diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go new file mode 100644 index 000000000..331b3beae --- /dev/null +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -0,0 +1,121 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +*/ + +package bl + +import ( + "context" + "net" + "testing" + "time" + "vermeer/apps/common" + "vermeer/apps/master/schedules" + "vermeer/apps/master/workers" + pb "vermeer/apps/protos" + "vermeer/apps/structure" + "vermeer/apps/version" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/peer" +) + +type registrationWindowWorker struct { + pb.UnimplementedWorkerServer + started chan struct{} + release chan struct{} +} + +func (w *registrationWindowWorker) RuntimeAction(context.Context, *pb.RuntimeActionReq) (*pb.RuntimeActionResp, error) { + close(w.started) + <-w.release + return &pb.RuntimeActionResp{Response: &pb.RuntimeActionResp_MemoryLimitResp{ + MemoryLimitResp: &pb.SetMemoryLimitResp{}, + }}, nil +} + +// This executes real SayHelloMaster while its ordinary memory-limit RPC is pending. +// No worker task stream can exist yet: the worker only opens those after hello returns. +func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { + previousWorkers := common.PrometheusMetrics.WorkerCnt + t.Cleanup(func() { common.PrometheusMetrics.WorkerCnt = previousWorkers }) + common.PrometheusMetrics.WorkerCnt = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "test_registration_workers"}, nil) + workers.WorkerManager.Init() + structure.SpaceManager.Init() + graphMgr.Init() + ServerMgr.Init() + Scheduler.resourceManager = &schedules.SchedulerResourceManager{} + Scheduler.resourceManager.Init() + Scheduler.taskManager = new(schedules.SchedulerTaskManager).Init() + Scheduler.algorithmManager = &schedules.SchedulerAlgorithmManager{} + Scheduler.algorithmManager.Init() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + w := ®istrationWindowWorker{started: make(chan struct{}), release: make(chan struct{})} + server := grpc.NewServer() + pb.RegisterWorkerServer(server, w) + go server.Serve(listener) + defer server.Stop() + returned := make(chan error, 1) + ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: listener.Addr()}) + go func() { + _, err := new(ServerHandler).SayHelloMaster(ctx, &pb.HelloMasterReq{ + WorkerPeer: listener.Addr().String(), Version: version.Version, + }) + returned <- err + }() + select { + case <-w.started: + case <-time.After(3 * time.Second): + t.Fatal("registration did not reach memory RPC") + } + defer func() { + close(w.release) + select { + case err := <-returned: + if err != nil { + t.Error(err) + } + case <-time.After(time.Second): + t.Error("hello did not return") + } + }() + if workers.WorkerManager.HasAvailableWorkers("current", nil) { + t.Fatal("hello still pending: reverse dial must not advertise task readiness") + } + task := &structure.TaskInfo{ID: 101, SpaceName: "current", GraphName: "graph", Type: structure.TaskTypeLoad} + agent, status, _, err := new(schedules.Broker).Init().ApplyAgent(task) + if err != nil || agent != nil || status != schedules.AgentStatusWorkerNotReady { + t.Fatalf("scheduler admitted a worker before its task streams: %v %v", status, err) + } + registered := workers.WorkerManager.GetAllWorkers() + if len(registered) != 1 { + t.Fatal("wrong registration count") + } + worker := registered[0] + t.Cleanup(worker.Close) + if ServerMgr.LoadServer(worker.Name) != nil { + t.Fatal("unexpected load stream") + } + if err := sendLoadingReq(task, registered, func(*workers.WorkerClient) *LoadingTaskReq { + return &LoadingTaskReq{} + }); err == nil { + t.Fatal("missing task transport must return an error") + } +} diff --git a/vermeer/apps/master/schedules/broker.go b/vermeer/apps/master/schedules/broker.go index 435219dc9..1493d561d 100644 --- a/vermeer/apps/master/schedules/broker.go +++ b/vermeer/apps/master/schedules/broker.go @@ -127,22 +127,15 @@ func (b *Broker) ApplyAgent(taskInfo *structure.TaskInfo, forceApply ...bool) (* // } func (b *Broker) isWorkersReady(workers map[string]*workers.WorkerClient) bool { - ok := false + if len(workers) == 0 { + return false + } for _, w := range workers { - if w.Connection == nil { - ok = false - break - } - - if w.Connection.GetState().String() == "READY" { - ok = true - } else { - ok = false - break + if !workerMgr.IsWorkerReady(w) { + return false } } - - return ok + return true } func (b *Broker) isAgentBusy(agent *Agent) bool { diff --git a/vermeer/apps/master/workers/worker_availability_test.go b/vermeer/apps/master/workers/worker_availability_test.go index 5a6ca594e..c4b49ca5a 100644 --- a/vermeer/apps/master/workers/worker_availability_test.go +++ b/vermeer/apps/master/workers/worker_availability_test.go @@ -58,6 +58,12 @@ func readyWorkerConnection(t *testing.T) *grpc.ClientConn { } } +func readyTaskStreams() [taskStreamCount]context.Context { + return [taskStreamCount]context.Context{ + context.Background(), context.Background(), context.Background(), + } +} + func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { connection := readyWorkerConnection(t) cases := []struct { @@ -79,7 +85,8 @@ func TestWorkerAvailabilityRespectsSpaceAllocation(t *testing.T) { t.Run(test.name, func(t *testing.T) { manager := workerManager{ workersByName: map[string]*WorkerClient{ - "worker": {Name: "worker", Group: test.group, Connection: connection}, + "worker": {Name: "worker", Group: test.group, Connection: connection, + taskStreams: readyTaskStreams()}, }, groupMapper: test.mappings, } @@ -100,12 +107,21 @@ func TestWorkerAvailabilityHandlesRegistrationAndDisconnect(t *testing.T) { if manager.HasAvailableWorkers("current", nil) { t.Fatal("missing worker must not be ready") } - manager.workersByName["worker"] = &WorkerClient{Group: "$"} + manager.workersByName["worker"] = &WorkerClient{Name: "worker", Group: "$"} if manager.HasAvailableWorkers("current", nil) { t.Fatal("unconnected worker must not be ready") } worker := manager.workersByName["worker"] manager.SetWorkerConnection(worker, connection) + if manager.HasAvailableWorkers("current", nil) { + t.Fatal("reverse dial alone must not advertise a task-ready worker") + } + for stream := LoadTaskStream; stream < taskStreamCount; stream++ { + manager.SetTaskStream(worker, stream, context.Background()) + if stream < SuperStepStream && manager.HasAvailableWorkers("current", nil) { + t.Fatal("partially registered task streams must not be ready") + } + } if !manager.HasAvailableWorkers("current", nil) { t.Fatal("registered ready worker must be available") } @@ -132,9 +148,9 @@ func TestWorkerAvailabilityWaitsForSelectedGroup(t *testing.T) { connection := readyWorkerConnection(t) manager := workerManager{ workersByName: map[string]*WorkerClient{ - "first": {Group: "assigned", Connection: connection}, - "second": {Group: "assigned"}, - "common": {Group: "$", Connection: connection}, + "first": {Name: "first", Group: "assigned", Connection: connection, taskStreams: readyTaskStreams()}, + "second": {Name: "second", Group: "assigned", taskStreams: readyTaskStreams()}, + "common": {Name: "common", Group: "$", Connection: connection, taskStreams: readyTaskStreams()}, }, groupMapper: map[string]string{"current": "assigned"}, } @@ -150,9 +166,42 @@ func TestWorkerAvailabilityWaitsForSelectedGroup(t *testing.T) { if manager.HasAvailableWorkers("current", nil) { t.Fatal("an empty explicit group cannot fall back to common workers") } - manager.workersByName["graph-worker"] = &WorkerClient{Group: "graph-group", Connection: connection} + manager.workersByName["graph-worker"] = &WorkerClient{ + Name: "graph-worker", Group: "graph-group", Connection: connection, taskStreams: readyTaskStreams(), + } manager.groupMapper[manager.toOwnerKey("current", "graph")] = "graph-group" if !manager.HasAvailableWorkers("current", []string{"graph"}) { t.Fatal("an existing graph's explicit ready group remains an available workload") } } + +func TestWorkerAvailabilityTracksTaskStreamCancellationAndReplacement(t *testing.T) { + worker := &WorkerClient{Name: "worker", Group: "$", Connection: readyWorkerConnection(t), + taskStreams: readyTaskStreams()} + manager := workerManager{workersByName: map[string]*WorkerClient{"worker": worker}} + previous, cancel := context.WithCancel(context.Background()) + manager.SetTaskStream(worker, LoadTaskStream, previous) + if !manager.IsWorkerReady(worker) { + t.Fatal("complete worker must be schedulable") + } + cancel() + if manager.IsWorkerReady(worker) || manager.HasAvailableWorkers("current", nil) { + t.Fatal("a closed task stream must disable scheduling and advertised availability") + } + replacement, closeReplacement := context.WithCancel(context.Background()) + defer closeReplacement() + manager.SetTaskStream(worker, LoadTaskStream, replacement) + manager.ClearTaskStream(worker, LoadTaskStream, previous) + if !manager.IsWorkerReady(worker) { + t.Fatal("old stream cleanup must not clear its replacement") + } + manager.ClearTaskStream(worker, LoadTaskStream, replacement) + if manager.IsWorkerReady(worker) { + t.Fatal("cleared task stream must not be schedulable") + } + manager.workersByName[worker.Name] = &WorkerClient{Name: worker.Name} + manager.SetTaskStream(worker, LoadTaskStream, replacement) + if manager.IsWorkerReady(worker) { + t.Fatal("a removed worker instance must not be schedulable") + } +} diff --git a/vermeer/apps/master/workers/worker_manager.go b/vermeer/apps/master/workers/worker_manager.go index 741736b2c..53773c3fc 100644 --- a/vermeer/apps/master/workers/worker_manager.go +++ b/vermeer/apps/master/workers/worker_manager.go @@ -18,6 +18,7 @@ under the License. package workers import ( + "context" "encoding/json" "fmt" "math/rand" @@ -40,6 +41,15 @@ const ( prefixMapper = "WORKER_MAPPER" ) +type TaskStream uint8 + +const ( + LoadTaskStream TaskStream = iota + ComputeTaskStream + SuperStepStream + taskStreamCount +) + // WorkerClient type WorkerClient struct { Id int32 `json:"id,omitempty"` @@ -54,8 +64,9 @@ type WorkerClient struct { //LoadStream pb.Master_LoadGraphTaskServer `json:"-"` //ComputeStream pb.Master_ComputeTaskServer `json:"-"` //SuperStepStream pb.Master_SuperStepServer `json:"-"` - Connection *grpc.ClientConn `json:"-"` - Session pb.WorkerClient `json:"-"` + Connection *grpc.ClientConn `json:"-"` + Session pb.WorkerClient `json:"-"` + taskStreams [taskStreamCount]context.Context } func (wc *WorkerClient) Close() { @@ -239,8 +250,41 @@ func (wm *workerManager) SetWorkerConnection(worker *WorkerClient, connection *g worker.Connection = connection } +// Task streams are published only after their dispatch entry has been installed. +func (wm *workerManager) SetTaskStream(worker *WorkerClient, stream TaskStream, ctx context.Context) { + defer wm.Unlock(wm.Lock()) + if worker != nil && wm.workersByName[worker.Name] == worker { + worker.taskStreams[stream] = ctx + } +} + +func (wm *workerManager) ClearTaskStream(worker *WorkerClient, stream TaskStream, ctx context.Context) { + defer wm.Unlock(wm.Lock()) + if worker != nil && wm.workersByName[worker.Name] == worker && worker.taskStreams[stream] == ctx { + worker.taskStreams[stream] = nil + } +} + +func (wm *workerManager) IsWorkerReady(worker *WorkerClient) bool { + defer wm.Unlock(wm.Lock()) + return wm.workerReadyLocked(worker) +} + +func (wm *workerManager) workerReadyLocked(worker *WorkerClient) bool { + if worker == nil || wm.workersByName[worker.Name] != worker || + worker.Connection == nil || worker.Connection.GetState() != connectivity.Ready { + return false + } + for _, ctx := range worker.taskStreams { + if ctx == nil || ctx.Err() != nil { + return false + } + } + return true +} + // HasAvailableWorkers follows existing allocation precedence without allocating. -// Every registered member of a candidate group must have a ready connection. +// Every registered member needs both a ready reverse connection and live task streams. func (wm *workerManager) HasAvailableWorkers(space string, graphNames []string) bool { defer wm.Unlock(wm.Lock()) groups := make(map[string]bool) @@ -261,7 +305,7 @@ func (wm *workerManager) HasAvailableWorkers(space string, graphNames []string) continue } found = true - if worker.Connection == nil || worker.Connection.GetState() != connectivity.Ready { + if !wm.workerReadyLocked(worker) { ready = false break } From cea93fffc24686ac112a6b0495cabdb2df8a2dd4 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 17:54:23 +0000 Subject: [PATCH 10/19] ci(vermeer): pin ASF-approved Docker actions --- .github/workflows/vermeer-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index eba9cb77d..6130c0178 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -99,10 +99,10 @@ jobs: uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - name: Build Docker image (dry-run) - uses: docker/build-push-action@v6 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: vermeer push: false From b5c1693f27779aeefc0098833368fbfc5448d544 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 22 Oct 2026 18:27:48 +0000 Subject: [PATCH 11/19] fix(vermeer): persist missing task-stream dispatch failures --- vermeer/apps/master/bl/task_bl.go | 4 +- .../master/bl/worker_availability_test.go | 41 +++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/vermeer/apps/master/bl/task_bl.go b/vermeer/apps/master/bl/task_bl.go index 44977823a..5c7838ba7 100644 --- a/vermeer/apps/master/bl/task_bl.go +++ b/vermeer/apps/master/bl/task_bl.go @@ -346,7 +346,9 @@ func sendLoadingReq(task *structure.TaskInfo, workerClients []*WorkerClient, app //err := wc.loadServer.SendLoadReq(apply(wc)) server := ServerMgr.LoadServer(wc.Name) if server == nil { - return fmt.Errorf("worker %s load task stream is unavailable", wc.Name) + err := fmt.Errorf("worker %s load task stream is unavailable", wc.Name) + taskMgr.SetError(task, err.Error()) + return err } err := server.SendLoadReq(apply(wc)) if err != nil { diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go index 331b3beae..be1b2597e 100644 --- a/vermeer/apps/master/bl/worker_availability_test.go +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -52,11 +52,25 @@ func (w *registrationWindowWorker) RuntimeAction(context.Context, *pb.RuntimeAct // No worker task stream can exist yet: the worker only opens those after hello returns. func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { previousWorkers := common.PrometheusMetrics.WorkerCnt - t.Cleanup(func() { common.PrometheusMetrics.WorkerCnt = previousWorkers }) + previousTasks := common.PrometheusMetrics.TaskCnt + previousRunning := common.PrometheusMetrics.TaskRunningCnt + t.Cleanup(func() { + common.PrometheusMetrics.WorkerCnt = previousWorkers + common.PrometheusMetrics.TaskCnt = previousTasks + common.PrometheusMetrics.TaskRunningCnt = previousRunning + }) common.PrometheusMetrics.WorkerCnt = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "test_registration_workers"}, nil) + common.PrometheusMetrics.TaskCnt = prometheus.NewCounterVec( + prometheus.CounterOpts{Name: "test_registration_tasks"}, []string{"type"}) + common.PrometheusMetrics.TaskRunningCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_registration_running"}, []string{"type"}) workers.WorkerManager.Init() structure.SpaceManager.Init() graphMgr.Init() + taskMgr.Init() + if err := taskMgr.InitStore(); err != nil { + t.Fatal(err) + } ServerMgr.Init() Scheduler.resourceManager = &schedules.SchedulerResourceManager{} Scheduler.resourceManager.Init() @@ -99,7 +113,15 @@ func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { if workers.WorkerManager.HasAvailableWorkers("current", nil) { t.Fatal("hello still pending: reverse dial must not advertise task readiness") } - task := &structure.TaskInfo{ID: 101, SpaceName: "current", GraphName: "graph", Type: structure.TaskTypeLoad} + task, err := taskMgr.CreateTask("current", structure.TaskTypeLoad, 0) + if err != nil { + t.Fatal(err) + } + task.GraphName = "graph" + task.CreateType = structure.TaskCreateSync + if _, err := taskMgr.AddTask(task); err != nil { + t.Fatal(err) + } agent, status, _, err := new(schedules.Broker).Init().ApplyAgent(task) if err != nil || agent != nil || status != schedules.AgentStatusWorkerNotReady { t.Fatalf("scheduler admitted a worker before its task streams: %v %v", status, err) @@ -113,9 +135,20 @@ func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { if ServerMgr.LoadServer(worker.Name) != nil { t.Fatal("unexpected load stream") } - if err := sendLoadingReq(task, registered, func(*workers.WorkerClient) *LoadingTaskReq { + err = sendLoadingReq(task, registered, func(*workers.WorkerClient) *LoadingTaskReq { return &LoadingTaskReq{} - }); err == nil { + }) + if err == nil { t.Fatal("missing task transport must return an error") } + if task.State != structure.TaskStateError || task.ErrMessage != err.Error() { + t.Fatalf("failed synchronous dispatch left state=%s, message=%q", task.State, task.ErrMessage) + } + persisted, readErr := taskMgr.ReadTask(task.ID) + if readErr != nil { + t.Fatal(readErr) + } + if persisted.State != structure.TaskStateError || persisted.ErrMessage != task.ErrMessage { + t.Fatalf("persisted failed task has state=%s, message=%q", persisted.State, persisted.ErrMessage) + } } From 81e1277e6b19e10e207b69eecb71ff4d25e3367a Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 04:20:59 +0000 Subject: [PATCH 12/19] fix(vermeer): stop partial loads and cover readiness authentication --- vermeer/README.md | 3 + vermeer/apps/common/http_server.go | 4 + vermeer/apps/master/bl/task_bl.go | 28 ++++-- .../master/bl/worker_availability_test.go | 99 +++++++++++++++++++ .../services/http_worker_availability_test.go | 63 ++++++++++++ 5 files changed, 189 insertions(+), 8 deletions(-) diff --git a/vermeer/README.md b/vermeer/README.md index 5883ed7bf..626f51c0b 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -360,6 +360,9 @@ Worker availability uses the normal space credential and returns only common pool. It accepts no caller-selected space and does not allocate workers or require an administrator token. Global worker registration alone does not establish readiness for a particular space. +A group preallocated to a graph absent from that space's Master graph metadata +is not counted until the graph appears there; the scheduler can still use the +allocation for that graph's first load. ### Example: Run PageRank diff --git a/vermeer/apps/common/http_server.go b/vermeer/apps/common/http_server.go index 5a5c3d28f..a9a0f8502 100644 --- a/vermeer/apps/common/http_server.go +++ b/vermeer/apps/common/http_server.go @@ -33,6 +33,10 @@ type Sentinel struct { srv *http.Server } +func (sen *Sentinel) ServeHTTP(w http.ResponseWriter, r *http.Request) { + sen.engine.ServeHTTP(w, r) +} + func (sen *Sentinel) Init(peer string, debugMode string) { if debugMode == "release" { gin.SetMode(gin.ReleaseMode) diff --git a/vermeer/apps/master/bl/task_bl.go b/vermeer/apps/master/bl/task_bl.go index 5c7838ba7..57237f87f 100644 --- a/vermeer/apps/master/bl/task_bl.go +++ b/vermeer/apps/master/bl/task_bl.go @@ -335,7 +335,23 @@ func doLoadingTask(graph *structure.VermeerGraph, task *structure.TaskInfo, work return nil } -func sendLoadingReq(task *structure.TaskInfo, workerClients []*WorkerClient, apply Function[*WorkerClient, *LoadingTaskReq]) error { +func sendLoadingReq(task *structure.TaskInfo, workerClients []*WorkerClient, + apply Function[*WorkerClient, *LoadingTaskReq]) (err error) { + dispatched := make([]*LoadGraphServer, 0, len(workerClients)) + defer func() { + if err == nil { + return + } + taskMgr.SetError(task, err.Error()) + // Stop earlier dispatches using their original streams. + // Cleanup is best effort and must not replace the original dispatch error. + for _, server := range dispatched { + if stopErr := server.AsyncLoad(task.ID, pb.LoadStep_Error, "", task.GraphName, + task.SpaceName, nil, nil); stopErr != nil { + logrus.Errorf("failed to stop partially dispatched load task %d: %v", task.ID, stopErr) + } + } + }() for _, wc := range workerClients { taskWorker := structure.TaskWorker{ @@ -346,16 +362,12 @@ func sendLoadingReq(task *structure.TaskInfo, workerClients []*WorkerClient, app //err := wc.loadServer.SendLoadReq(apply(wc)) server := ServerMgr.LoadServer(wc.Name) if server == nil { - err := fmt.Errorf("worker %s load task stream is unavailable", wc.Name) - taskMgr.SetError(task, err.Error()) - return err + return fmt.Errorf("worker %s load task stream is unavailable", wc.Name) } - err := server.SendLoadReq(apply(wc)) - if err != nil { - //task.SetState(structure.TaskStateError) - taskMgr.SetError(task, err.Error()) + if err := server.SendLoadReq(apply(wc)); err != nil { return err } + dispatched = append(dispatched, server) } return nil diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go index be1b2597e..1317895ed 100644 --- a/vermeer/apps/master/bl/worker_availability_test.go +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -19,6 +19,8 @@ package bl import ( "context" + "errors" + "io" "net" "testing" "time" @@ -40,6 +42,25 @@ type registrationWindowWorker struct { release chan struct{} } +type dispatchTestStream struct { + grpc.ServerStream + requests []*pb.LoadGraphTaskResp + sendErr error + stopErr error +} + +func (s *dispatchTestStream) Send(req *pb.LoadGraphTaskResp) error { + s.requests = append(s.requests, req) + if req.Step == pb.LoadStep_Error { + return s.stopErr + } + return s.sendErr +} + +func (s *dispatchTestStream) Recv() (*pb.LoadGraphTaskReq, error) { + return nil, io.EOF +} + func (w *registrationWindowWorker) RuntimeAction(context.Context, *pb.RuntimeActionReq) (*pb.RuntimeActionResp, error) { close(w.started) <-w.release @@ -151,4 +172,82 @@ func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { if persisted.State != structure.TaskStateError || persisted.ErrMessage != task.ErrMessage { t.Fatalf("persisted failed task has state=%s, message=%q", persisted.State, persisted.ErrMessage) } + + // Reuse the isolated task store to check failures after earlier workers accepted a load. + for _, test := range []struct { + name string + missing bool + failedSend bool + failedStop bool + }{ + {name: "dispatch_missing_stream", missing: true}, + {name: "dispatch_send_failure", failedSend: true}, + {name: "dispatch_cleanup_failure", missing: true, failedStop: true}, + {name: "dispatch_success"}, + } { + t.Run(test.name, func(t *testing.T) { + ServerMgr.Init() + defer ServerMgr.Init() + first, second, third := &dispatchTestStream{}, &dispatchTestStream{}, &dispatchTestStream{} + dispatchErr := errors.New("dispatch failed") + if test.failedSend { + third.sendErr = dispatchErr + } + if test.failedStop { + first.stopErr = errors.New("cleanup failed") + } + ServerMgr.loadSevers["first"] = &LoadGraphServer{streamServer: first} + ServerMgr.loadSevers["second"] = &LoadGraphServer{streamServer: second} + if !test.missing { + ServerMgr.loadSevers["third"] = &LoadGraphServer{streamServer: third} + } + task, err := taskMgr.CreateTask("dispatch-space", structure.TaskTypeLoad, 0) + if err != nil { + t.Fatal(err) + } + task.GraphName = "dispatch-graph" + if _, err := taskMgr.AddTask(task); err != nil { + t.Fatal(err) + } + err = sendLoadingReq(task, []*workers.WorkerClient{ + {Name: "first"}, {Name: "second"}, {Name: "third"}, + }, func(*workers.WorkerClient) *LoadingTaskReq { + return &LoadingTaskReq{TaskId: task.ID, Step: pb.LoadStep_Vertex, + SpaceName: task.SpaceName, GraphName: task.GraphName} + }) + failed := test.missing || test.failedSend + if (err != nil) != failed { + t.Fatalf("dispatch error = %v, want failed = %v", err, failed) + } + if test.failedSend && !errors.Is(err, dispatchErr) { + t.Fatalf("original dispatch error was replaced: %v", err) + } + if failed { + persisted, readErr := taskMgr.ReadTask(task.ID) + if readErr != nil { + t.Fatal(readErr) + } + if task.State != structure.TaskStateError || task.ErrMessage != err.Error() || + persisted.State != structure.TaskStateError || persisted.ErrMessage != err.Error() { + t.Fatal("partial dispatch failure must retain the original error in memory and storage") + } + } + for _, stream := range []*dispatchTestStream{first, second} { + want := 1 + if failed { + want = 2 + } + if len(stream.requests) != want || stream.requests[0].Step != pb.LoadStep_Vertex { + t.Fatalf("successful worker received %d messages, want %d", len(stream.requests), want) + } + if failed { + stop := stream.requests[1] + if stop.Step != pb.LoadStep_Error || stop.TaskId != task.ID || + stop.SpaceName != task.SpaceName || stop.GraphName != task.GraphName { + t.Fatalf("wrong cleanup scope: %v", stop) + } + } + } + }) + } } diff --git a/vermeer/apps/master/services/http_worker_availability_test.go b/vermeer/apps/master/services/http_worker_availability_test.go index 0c73b349e..0a5bb9b1d 100644 --- a/vermeer/apps/master/services/http_worker_availability_test.go +++ b/vermeer/apps/master/services/http_worker_availability_test.go @@ -22,6 +22,7 @@ import ( "net/http" "net/http/httptest" "testing" + "vermeer/apps/auth" "vermeer/apps/common" "vermeer/apps/structure" @@ -70,3 +71,65 @@ func TestWorkerAvailabilityUsesAuthenticatedSpace(t *testing.T) { t.Fatalf("response must contain only readiness: %v", body) } } + +func TestWorkerAvailabilityRequiresAuthentication(t *testing.T) { + previousFactor := common.GetConfig("auth_token_factor") + previousMetrics := common.PrometheusMetrics + previousRegisterer, previousGatherer := prometheus.DefaultRegisterer, prometheus.DefaultGatherer + previousMode := gin.Mode() + registry := prometheus.NewRegistry() + prometheus.DefaultRegisterer, prometheus.DefaultGatherer = registry, registry + const factor = "synthetic-worker-availability-factor" + common.SetConfig("auth_token_factor", factor) + auth.Init() + GraphMgr.Init() + t.Cleanup(func() { + common.SetConfig("auth_token_factor", previousFactor) + common.PrometheusMetrics = previousMetrics + prometheus.DefaultRegisterer, prometheus.DefaultGatherer = previousRegisterer, previousGatherer + gin.SetMode(previousMode) + GraphMgr.Init() + }) + factory := auth.TokenFactory{Factor: factor} + token := factory.NewToken("space-user", "own-space", "") + factory.Sign(token) + encoded, err := json.Marshal(token) + if err != nil { + t.Fatal(err) + } + sentinel := &common.Sentinel{} + sentinel.Init("127.0.0.1:0", "release") + SetRouters(sentinel, auth.TokenFilter) + for _, path := range []string{"/workers/availability", "/api/v1/workers/availability"} { + for _, test := range []struct { + name string + header string + status int + }{ + {name: "missing token", status: http.StatusUnauthorized}, + {name: "invalid token", header: "Bearer invalid", status: http.StatusUnauthorized}, + {name: "signed token", header: "Bearer " + auth.ToBase58(string(encoded)), status: http.StatusOK}, + } { + t.Run(path+"/"+test.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, path, nil) + if test.header != "" { + req.Header.Set(auth.HeaderKey, test.header) + } + response := httptest.NewRecorder() + sentinel.ServeHTTP(response, req) + if response.Code != test.status { + t.Fatalf("status = %d, want %d", response.Code, test.status) + } + if test.status == http.StatusOK { + var body map[string]any + if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + if len(body) != 1 || body["available"] != false { + t.Fatalf("unexpected readiness response: %v", body) + } + } + }) + } + } +} From 819b34de39bf4c0e26b88b3465d7835f67a5aea9 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 05:57:06 +0000 Subject: [PATCH 13/19] fix(vermeer): bind deployment discovery and serialize load sends --- vermeer/README.md | 12 +- vermeer/apps/common/hugegraph_credentials.go | 5 +- vermeer/apps/common/hugegraph_tools.go | 34 +++-- vermeer/apps/common/pd_peers.go | 70 ++++++++++ vermeer/apps/common/pd_peers_test.go | 128 ++++++++++++++++++ vermeer/apps/master/bl/grpc_handlers.go | 4 + .../master/bl/worker_availability_test.go | 67 +++++++++ .../structure/property_credentials_test.go | 20 +++ 8 files changed, 324 insertions(+), 16 deletions(-) create mode 100644 vermeer/apps/common/pd_peers.go create mode 100644 vermeer/apps/common/pd_peers_test.go diff --git a/vermeer/README.md b/vermeer/README.md index 626f51c0b..260b205ba 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -241,7 +241,8 @@ auth = none auth_token_factor = 1234 ``` -**Note**: HugeGraph connection details (`pd_peers`, `server`, `graph`) are provided in the graph load API request, not in the configuration file. See [HugeGraph Integration](#hugegraph-integration) section for details. +**Note**: Graph names and legacy HugeGraph connection details are supplied in the graph load request. +Deployment credentials require the trusted PD configuration described below. ### Generating Auth Token @@ -278,6 +279,15 @@ is sent as the internal `vermeer` credential and is never added to task paramete When no file is configured, the legacy credential remains available for older PD deployments; it does not authenticate to a modern PD with a different shared secret. +When either deployment credential file is configured, configure both Master and +Worker with `pd_peers=["pd.internal:8686"]`, or set `VERMEER_PD_PEERS` to that JSON +array (environment takes precedence). These deployment addresses replace task +PD addresses for load and output discovery. Missing or invalid deployment peers +fail before discovery; no credential file is read for PD discovery in that case. +Schema service credentials are used only with Servers discovered through these +PD addresses, and Schema HTTP requests do not follow redirects. Without deployment +credentials or peers, task-provided PD addresses retain their legacy behavior. + The current PD gRPC transport is plaintext. Keep these connections on a trusted private network or loopback; deployments crossing untrusted networks must provide transport encryption. The password file configures request credentials, not TLS. diff --git a/vermeer/apps/common/hugegraph_credentials.go b/vermeer/apps/common/hugegraph_credentials.go index 7241b3126..5f5633d2c 100644 --- a/vermeer/apps/common/hugegraph_credentials.go +++ b/vermeer/apps/common/hugegraph_credentials.go @@ -27,10 +27,7 @@ import ( // ResolveHugegraphCredentials keeps deployment credentials out of persisted task parameters. // Without a configured file, legacy callers retain their task-provided credentials. func ResolveHugegraphCredentials(username, password string) (string, string, error) { - file := os.Getenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE") - if file == "" { - file, _ = GetConfigDefault("hugegraph_credentials_file", "").(string) - } + file := deploymentConfig("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", "hugegraph_credentials_file") if file == "" { return username, password, nil } diff --git a/vermeer/apps/common/hugegraph_tools.go b/vermeer/apps/common/hugegraph_tools.go index 8aa5718c7..dc40f991b 100644 --- a/vermeer/apps/common/hugegraph_tools.go +++ b/vermeer/apps/common/hugegraph_tools.go @@ -54,9 +54,12 @@ const ( type HgVertexIDType uint16 func FindValidPD(pdIPAddress []string) (string, error) { + pdIPAddress, err := resolvePDPeers(pdIPAddress) + if err != nil { + return "", err + } // retry 3 times to find valid pd address var pdIPAddr string - var err error for i := 0; i < 3; i++ { pdIPAddr, err = findValidPD(pdIPAddress) if err == nil && pdIPAddr != "" { @@ -109,6 +112,9 @@ func findValidPD(pdIPAddress []string) (string, error) { } func FindServerAddr(pdIPAddress string, hgName string, username string, password string) (string, error) { + if err := requireTrustedPD(pdIPAddress); err != nil { + return "", err + } // retry 3 times to find valid server address var serverAddr string var err error @@ -189,12 +195,12 @@ func testServerIsValid(serverAdds []string, hgSpace, hGraph, username, password defer wg.Done() url := fmt.Sprintf("%v/graphspaces/%v/graphs/%v/schema?format=json", addr, hgSpace, hGraph) req, err := http.NewRequest(http.MethodGet, url, nil) - req.SetBasicAuth(username, password) if err != nil { return } - req.WithContext(ctx) - client := &http.Client{Timeout: 5 * time.Second} + req.SetBasicAuth(username, password) + req = req.WithContext(ctx) + client := schemaHTTPClient() resp, err := client.Do(req) if err != nil { return @@ -233,15 +239,15 @@ func GetHugegraphSchema(serverAddr, hgName, username, password string) (map[stri func getHugegraphSchema(url, username, password string) (map[string]any, error) { req, err := http.NewRequest(http.MethodGet, url, nil) - req.SetBasicAuth(username, password) if err != nil { logrus.Errorf("create http request failed, %v", err.Error()) return nil, fmt.Errorf("create http request failed, %v", err.Error()) } + req.SetBasicAuth(username, password) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - req.WithContext(ctx) - client := &http.Client{Timeout: 5 * time.Second} + req = req.WithContext(ctx) + client := schemaHTTPClient() resp, err := client.Do(req) if err != nil { logrus.Errorf("http request failed, %v", err.Error()) @@ -265,6 +271,15 @@ func getHugegraphSchema(url, username, password string) (map[string]any, error) return propertyFromHg, nil } +func schemaHTTPClient() *http.Client { + return &http.Client{ + Timeout: 5 * time.Second, + CheckRedirect: func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + func SplitHgName(hgName string) (string, string, error) { hgSplit := strings.Split(hgName, "/") if len(hgSplit) != 3 { @@ -333,10 +348,7 @@ func (pa *PDAuthority) SetAuthority(ctx context.Context) (context.Context, error pa.locker.RLock() defer pa.locker.RUnlock() credential := CredentialValue // Legacy PD compatibility when no file is configured. - passwordFile := os.Getenv("VERMEER_PD_PASSWORD_FILE") - if passwordFile == "" { - passwordFile, _ = GetConfigDefault("pd_password_file", "").(string) - } + passwordFile := deploymentConfig("VERMEER_PD_PASSWORD_FILE", "pd_password_file") if passwordFile != "" { password, err := os.ReadFile(passwordFile) if err != nil { diff --git a/vermeer/apps/common/pd_peers.go b/vermeer/apps/common/pd_peers.go new file mode 100644 index 000000000..a2a48daf3 --- /dev/null +++ b/vermeer/apps/common/pd_peers.go @@ -0,0 +1,70 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "encoding/json" + "errors" + "net" + "os" + "slices" + "strconv" +) + +func deploymentConfig(environment, key string) string { + if value := os.Getenv(environment); value != "" { + return value + } + value, _ := GetConfigDefault(key, "").(string) + return value +} + +// Deployment discovery takes precedence over task parameters on both Master and Worker. +func resolvePDPeers(taskPeers []string) ([]string, error) { + configured := deploymentConfig("VERMEER_PD_PEERS", "pd_peers") + if configured == "" { + if deploymentConfig("VERMEER_PD_PASSWORD_FILE", "pd_password_file") != "" || + deploymentConfig("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", "hugegraph_credentials_file") != "" { + return nil, errors.New("configure pd_peers when using deployment credentials") + } + return taskPeers, nil + } + var peers []string + if json.Unmarshal([]byte(configured), &peers) != nil || len(peers) == 0 { + return nil, errors.New("pd_peers must be a nonempty JSON array of host:port addresses") + } + for _, peer := range peers { + host, port, err := net.SplitHostPort(peer) + number, portErr := strconv.Atoi(port) + if err != nil || host == "" || portErr != nil || number < 1 || number > 65535 { + return nil, errors.New("pd_peers must contain valid host:port addresses") + } + } + return peers, nil +} + +func requireTrustedPD(peer string) error { + peers, err := resolvePDPeers([]string{peer}) + if err != nil { + return err + } + if !slices.Contains(peers, peer) { + return errors.New("PD address is outside configured pd_peers") + } + return nil +} diff --git a/vermeer/apps/common/pd_peers_test.go b/vermeer/apps/common/pd_peers_test.go new file mode 100644 index 000000000..d9ad25f37 --- /dev/null +++ b/vermeer/apps/common/pd_peers_test.go @@ -0,0 +1,128 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "net/http" + "net/http/httptest" + "path/filepath" + "reflect" + "strings" + "sync/atomic" + "testing" +) + +func isolatePDPeerConfig(t *testing.T) { + t.Helper() + for _, key := range []string{"pd_peers", "pd_password_file", "hugegraph_credentials_file"} { + old := GetConfigDefault(key, "") + SetConfig(key, "") + t.Cleanup(func() { SetConfig(key, old) }) + } + for _, key := range []string{"VERMEER_PD_PEERS", "VERMEER_PD_PASSWORD_FILE", "VERMEER_HUGEGRAPH_CREDENTIALS_FILE"} { + t.Setenv(key, "") + } +} + +func TestPDPeersDeploymentPrecedence(t *testing.T) { + isolatePDPeerConfig(t) + taskPeers := []string{"task-pd:8686"} + peers, err := resolvePDPeers(taskPeers) + if err != nil || !reflect.DeepEqual(peers, taskPeers) { + t.Fatal("unconfigured deployments must retain legacy task discovery") + } + SetConfig("pd_peers", `["configured-pd:8686"]`) + peers, err = resolvePDPeers(taskPeers) + if err != nil || !reflect.DeepEqual(peers, []string{"configured-pd:8686"}) { + t.Fatal("deployment peers must replace task peers") + } + t.Setenv("VERMEER_PD_PEERS", `["environment-pd:8686"]`) + peers, err = resolvePDPeers(taskPeers) + if err != nil || !reflect.DeepEqual(peers, []string{"environment-pd:8686"}) { + t.Fatal("environment peers must override configuration and task peers") + } +} + +func TestPDPeersDeploymentCredentialsRequireConfig(t *testing.T) { + for _, key := range []string{"pd_password_file", "hugegraph_credentials_file", + "VERMEER_PD_PASSWORD_FILE", "VERMEER_HUGEGRAPH_CREDENTIALS_FILE"} { + t.Run(key, func(t *testing.T) { + isolatePDPeerConfig(t) + missing := filepath.Join(t.TempDir(), "not-created") + if strings.HasPrefix(key, "VERMEER_") { + t.Setenv(key, missing) + } else { + SetConfig(key, missing) + } + // No endpoint and no credential file exist: validation must precede IO. + if _, err := FindValidPD(nil); err == nil || !strings.Contains(err.Error(), "configure pd_peers") { + t.Fatalf("expected deployment peer validation before discovery, got %v", err) + } + if err := requireTrustedPD("task-pd:8686"); err == nil { + t.Fatal("Server discovery must require deployment PD configuration too") + } + }) + } +} + +func TestPDPeersInvalidDeploymentFailsClosed(t *testing.T) { + isolatePDPeerConfig(t) + SetConfig("pd_peers", `["configured-pd:8686"]`) + for _, value := range []string{`null`, `[]`, `not-json`, `[""]`, `[null]`, + `["task-pd"]`, `["task-pd:0"]`, `["task-pd:65536"]`, `["task-pd:8686",""]`} { + t.Run(value, func(t *testing.T) { + t.Setenv("VERMEER_PD_PEERS", value) + if _, err := FindValidPD([]string{"task-pd:8686"}); err == nil { + t.Fatal("invalid deployment peers must not fall back to config or task peers") + } + }) + } +} + +func TestPDPeersConstrainServerDiscovery(t *testing.T) { + isolatePDPeerConfig(t) + t.Setenv("VERMEER_PD_PEERS", `["configured-pd:8686"]`) + if err := requireTrustedPD("configured-pd:8686"); err != nil { + t.Fatal(err) + } + if _, err := FindServerAddr("task-pd:8686", "DEFAULT/graph/g", "fixture-user", "fixture-password"); err == nil || !strings.Contains(err.Error(), "outside configured pd_peers") { + t.Fatal("Server discovery must reject an unconfigured PD before dialing") + } +} + +func TestHugegraphSchemaDoesNotFollowRedirects(t *testing.T) { + var redirected atomic.Int32 + destination := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + redirected.Add(1) + _, _ = w.Write([]byte(`{}`)) + })) + defer destination.Close() + source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, destination.URL, http.StatusFound) + })) + defer source.Close() + if addr := testServerIsValid([]string{source.URL}, "DEFAULT", "graph", "fixture-user", "fixture-password"); addr != "" { + t.Fatal("a redirect must not count as a discovered Schema Server") + } + if _, err := getHugegraphSchema(source.URL, "fixture-user", "fixture-password"); err == nil { + t.Fatal("a Schema redirect must be reported as an error") + } + if redirected.Load() != 0 { + t.Fatal("Schema discovery and reads must stay at the original Server") + } +} diff --git a/vermeer/apps/master/bl/grpc_handlers.go b/vermeer/apps/master/bl/grpc_handlers.go index d2cb3b750..0d8d0a80e 100644 --- a/vermeer/apps/master/bl/grpc_handlers.go +++ b/vermeer/apps/master/bl/grpc_handlers.go @@ -383,6 +383,7 @@ func (h *ServerHandler) UploadTPResult(ctx context.Context, in *pb.UploadTPResul type LoadGraphServer struct { streamServer pb.Master_LoadGraphTaskServer + sendMutex sync.Mutex } func (s *LoadGraphServer) SetServer(stream pb.Master_LoadGraphTaskServer) { @@ -435,6 +436,9 @@ func (s *LoadGraphServer) AsyncLoad( spaceName string, workers []string, params map[string]string) error { + // Dispatch, phase changes and error cleanup share this stream. + s.sendMutex.Lock() + defer s.sendMutex.Unlock() err := s.streamServer.Send(&pb.LoadGraphTaskResp{ Base: &pb.BaseResponse{}, LoadType: loadType, diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go index 1317895ed..604e17ccb 100644 --- a/vermeer/apps/master/bl/worker_availability_test.go +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -20,8 +20,10 @@ package bl import ( "context" "errors" + "fmt" "io" "net" + "sync/atomic" "testing" "time" "vermeer/apps/common" @@ -61,6 +63,71 @@ func (s *dispatchTestStream) Recv() (*pb.LoadGraphTaskReq, error) { return nil, io.EOF } +type serializedLoadTestStream struct { + dispatchTestStream + entered chan struct{} + release chan struct{} + second chan struct{} + active atomic.Int32 + peak atomic.Int32 + calls atomic.Int32 +} + +func (s *serializedLoadTestStream) Send(*pb.LoadGraphTaskResp) error { + active := s.active.Add(1) + defer s.active.Add(-1) + for peak := s.peak.Load(); active > peak; peak = s.peak.Load() { + if s.peak.CompareAndSwap(peak, active) { + break + } + } + if s.calls.Add(1) == 1 { + close(s.entered) + <-s.release + return s.sendErr + } + close(s.second) + return nil +} + +func TestWorkerAvailabilitySerializesLoadSends(t *testing.T) { + for _, failed := range []bool{false, true} { + t.Run(fmt.Sprintf("first_send_fails_%v", failed), func(t *testing.T) { + stream := &serializedLoadTestStream{ + entered: make(chan struct{}), release: make(chan struct{}), second: make(chan struct{}), + } + if failed { + stream.sendErr = errors.New("fixture send failure") + } + server := &LoadGraphServer{streamServer: stream} + first, second := make(chan error, 1), make(chan error, 1) + go func() { first <- server.SendLoadReq(&LoadingTaskReq{TaskId: 1, Step: pb.LoadStep_Vertex}) }() + <-stream.entered + started := make(chan struct{}) + go func() { + close(started) + second <- server.AsyncLoad(1, pb.LoadStep_Error, "", "graph", "space", nil, nil) + }() + <-started + select { + case <-stream.second: + t.Error("a second underlying Send entered before the first completed") + case <-time.After(50 * time.Millisecond): + } + close(stream.release) + if err := <-first; !errors.Is(err, stream.sendErr) { + t.Fatalf("original send error changed: %v", err) + } + if err := <-second; err != nil { + t.Fatal(err) + } + if stream.peak.Load() != 1 || stream.calls.Load() != 2 { + t.Fatal("all load sends must be serialized and the lock released after errors") + } + }) + } +} + func (w *registrationWindowWorker) RuntimeAction(context.Context, *pb.RuntimeActionReq) (*pb.RuntimeActionResp, error) { close(w.started) <-w.release diff --git a/vermeer/apps/structure/property_credentials_test.go b/vermeer/apps/structure/property_credentials_test.go index 0b0b0a828..6add947b2 100644 --- a/vermeer/apps/structure/property_credentials_test.go +++ b/vermeer/apps/structure/property_credentials_test.go @@ -30,6 +30,7 @@ import ( "testing" "google.golang.org/grpc" + "vermeer/apps/common" "vermeer/apps/options" pd "vermeer/apps/protos/hugegraph-pd-grpc" ) @@ -85,6 +86,7 @@ func TestHugegraphSchemaDeploymentCredentials(t *testing.T) { t.Fatal(err) } t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", credentials) + t.Setenv("VERMEER_PD_PEERS", `["`+listener.Addr().String()+`"]`) params := map[string]string{ "load.hg_pd_peers": `["` + listener.Addr().String() + `"]`, "load.hugegraph_name": "DEFAULT/fresh-graph/g", "load.use_property": "1", @@ -116,3 +118,21 @@ func TestHugegraphSchemaRejectsInvalidCredentialsBeforeDiscovery(t *testing.T) { t.Fatalf("expected credential error before discovery, got %v", err) } } + +func TestHugegraphSchemaRequiresDeploymentPD(t *testing.T) { + previousOptions := options.AllOptionsMap + options.Init() + t.Cleanup(func() { options.AllOptionsMap = previousOptions }) + previousPeers := common.GetConfigDefault("pd_peers", "") + common.SetConfig("pd_peers", "") + t.Cleanup(func() { common.SetConfig("pd_peers", previousPeers) }) + t.Setenv("VERMEER_PD_PEERS", "") + credentials := filepath.Join(t.TempDir(), "credentials.json") + if err := os.WriteFile(credentials, []byte(`{"username":"fixture-reader","password":"fixture-password"}`), 0600); err != nil { + t.Fatal(err) + } + t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", credentials) + if _, _, err := GetSchemaFromHugegraph(map[string]string{}); err == nil || !strings.Contains(err.Error(), "configure pd_peers") { + t.Fatal("deployment Schema credentials require deployment PD configuration before discovery") + } +} From 73a6f64646d2b12512f4f69ed9bd7fa6f13bb4d3 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 06:41:25 +0000 Subject: [PATCH 14/19] fix(vermeer): publish task streams and readiness atomically --- vermeer/apps/master/bl/grpc_handlers.go | 1 - vermeer/apps/master/bl/server_manager.go | 7 +- .../master/bl/worker_availability_test.go | 77 +++++++++++++++++++ .../structure/property_credentials_test.go | 3 +- 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/vermeer/apps/master/bl/grpc_handlers.go b/vermeer/apps/master/bl/grpc_handlers.go index 0d8d0a80e..4b5378c73 100644 --- a/vermeer/apps/master/bl/grpc_handlers.go +++ b/vermeer/apps/master/bl/grpc_handlers.go @@ -237,7 +237,6 @@ func (h *ServerHandler) SuperStep(req *pb.SuperStepReq, stream pb.Master_SuperSt return err } worker := workerMgr.GetWorker(name) - workerMgr.SetTaskStream(worker, workers.SuperStepStream, stream.Context()) defer workerMgr.ClearTaskStream(worker, workers.SuperStepStream, stream.Context()) <-stream.Context().Done() return nil diff --git a/vermeer/apps/master/bl/server_manager.go b/vermeer/apps/master/bl/server_manager.go index 7e2e27adf..e9856c51f 100644 --- a/vermeer/apps/master/bl/server_manager.go +++ b/vermeer/apps/master/bl/server_manager.go @@ -45,9 +45,9 @@ func (s *ServerManager) StartingLoadServer(workerName string, stream pb.Master_L loadServer := &LoadGraphServer{streamServer: stream} s.locker.Lock() s.loadSevers[workerName] = loadServer - s.locker.Unlock() worker := workerMgr.GetWorker(workerName) workerMgr.SetTaskStream(worker, workers.LoadTaskStream, stream.Context()) + s.locker.Unlock() defer func() { workerMgr.ClearTaskStream(worker, workers.LoadTaskStream, stream.Context()) s.locker.Lock() @@ -69,9 +69,9 @@ func (s *ServerManager) StartingComputeServer(workerName string, stream pb.Maste computeServer := &ComputeTaskServer{streamServer: stream} s.locker.Lock() s.computeSevers[workerName] = computeServer - s.locker.Unlock() worker := workerMgr.GetWorker(workerName) workerMgr.SetTaskStream(worker, workers.ComputeTaskStream, stream.Context()) + s.locker.Unlock() defer func() { workerMgr.ClearTaskStream(worker, workers.ComputeTaskStream, stream.Context()) s.locker.Lock() @@ -95,6 +95,9 @@ func (s *ServerManager) PutSuperStepServer(workerName string, stream pb.Master_S delete(s.superStepSevers, workerName) superStepServer := &SuperStepServer{streamServer: stream} s.superStepSevers[workerName] = superStepServer + // Keep dispatch installation and readiness publication in the same order. + worker := workerMgr.GetWorker(workerName) + workerMgr.SetTaskStream(worker, workers.SuperStepStream, stream.Context()) return nil } diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go index 604e17ccb..579263d41 100644 --- a/vermeer/apps/master/bl/worker_availability_test.go +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -35,6 +35,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" ) @@ -73,6 +74,81 @@ type serializedLoadTestStream struct { calls atomic.Int32 } +type publicationTestStream struct { + grpc.ServerStream + ctx context.Context + onRead func(int32) + reads atomic.Int32 +} + +func (s *publicationTestStream) Context() context.Context { + if s.onRead != nil { + s.onRead(s.reads.Add(1)) + } + return s.ctx +} + +func (s *publicationTestStream) Send(*pb.SuperStepResp) error { return nil } + +// This checks registration ordering with controlled handlers, not a live reconnect. +func testSuperStepPublication(t *testing.T, worker *workers.WorkerClient) { + ServerMgr.Init() + workerMgr.SetTaskStream(worker, workers.LoadTaskStream, context.Background()) + workerMgr.SetTaskStream(worker, workers.ComputeTaskStream, context.Background()) + oldCtx, cancelOld := context.WithCancel(context.Background()) + newCtx, cancelNew := context.WithCancel(context.Background()) + defer cancelOld() + defer cancelNew() + paused, release, published := make(chan struct{}), make(chan struct{}), make(chan struct{}) + oldStream := &publicationTestStream{ + ctx: metadata.NewIncomingContext(oldCtx, metadata.Pairs("worker_name", worker.Name)), + onRead: func(read int32) { + if read == 2 { + close(paused) + <-release + } + }, + } + newStream := &publicationTestStream{ + ctx: metadata.NewIncomingContext(newCtx, metadata.Pairs("worker_name", worker.Name)), + onRead: func(read int32) { + if read == 3 { + close(published) + } + }, + } + oldDone, newDone := make(chan error, 1), make(chan error, 1) + go func() { oldDone <- new(ServerHandler).SuperStep(&pb.SuperStepReq{}, oldStream) }() + <-paused + started := make(chan struct{}) + go func() { + close(started) + newDone <- new(ServerHandler).SuperStep(&pb.SuperStepReq{}, newStream) + }() + <-started + select { + case <-published: + t.Error("replacement registration overtook an unpublished dispatch entry") + case <-time.After(50 * time.Millisecond): + } + close(release) + <-published + cancelOld() + if err := <-oldDone; err != nil { + t.Error(err) + } + if !workerMgr.IsWorkerReady(worker) || ServerMgr.SuperStepServer(worker.Name).streamServer != newStream { + t.Error("old publication or cleanup replaced the live stream's readiness") + } + cancelNew() + if err := <-newDone; err != nil { + t.Error(err) + } + if workerMgr.IsWorkerReady(worker) { + t.Error("a canceled replacement must no longer be ready") + } +} + func (s *serializedLoadTestStream) Send(*pb.LoadGraphTaskResp) error { active := s.active.Add(1) defer s.active.Add(-1) @@ -220,6 +296,7 @@ func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { } worker := registered[0] t.Cleanup(worker.Close) + t.Run("superstep_publication_order", func(t *testing.T) { testSuperStepPublication(t, worker) }) if ServerMgr.LoadServer(worker.Name) != nil { t.Fatal("unexpected load stream") } diff --git a/vermeer/apps/structure/property_credentials_test.go b/vermeer/apps/structure/property_credentials_test.go index 6add947b2..38a36ab4a 100644 --- a/vermeer/apps/structure/property_credentials_test.go +++ b/vermeer/apps/structure/property_credentials_test.go @@ -132,7 +132,8 @@ func TestHugegraphSchemaRequiresDeploymentPD(t *testing.T) { t.Fatal(err) } t.Setenv("VERMEER_HUGEGRAPH_CREDENTIALS_FILE", credentials) - if _, _, err := GetSchemaFromHugegraph(map[string]string{}); err == nil || !strings.Contains(err.Error(), "configure pd_peers") { + _, _, err := GetSchemaFromHugegraph(map[string]string{}) + if err == nil || !strings.Contains(err.Error(), "configure pd_peers") { t.Fatal("deployment Schema credentials require deployment PD configuration before discovery") } } From 273e1ecf63bc739068ea08e4e2b9ff47334d94a5 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 08:18:49 +0000 Subject: [PATCH 15/19] fix(vermeer): authorize bulk loads and warn on PD overrides --- .github/workflows/vermeer-ci.yml | 2 +- vermeer/README.md | 15 ++ vermeer/apps/common/pd_peers.go | 8 + vermeer/apps/common/pd_peers_test.go | 44 +++++ .../apps/master/bl/load_authorization_test.go | 165 ++++++++++++++++++ vermeer/apps/master/bl/task_creator.go | 33 ++++ vermeer/apps/master/bl/task_starter.go | 5 + 7 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 vermeer/apps/master/bl/load_authorization_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index 6130c0178..3c08d2dd9 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -76,7 +76,7 @@ jobs: run: | go test -race ./apps/common go test -race ./apps/master/workers ./apps/master/services -run 'TestWorkerAvailability' - go test -race ./apps/master/bl -run 'TestWorkerAvailability' + go test -race ./apps/master/bl -run 'Test(WorkerAvailability|HugegraphLoadAuthorization)' go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex)' go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' diff --git a/vermeer/README.md b/vermeer/README.md index 260b205ba..525e9322c 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -288,6 +288,21 @@ Schema service credentials are used only with Servers discovered through these PD addresses, and Schema HTTP requests do not follow redirects. Without deployment credentials or peers, task-provided PD addresses retain their legacy behavior. +With Master `auth=token`, HugeGraph bulk load and reload tasks require the existing +Vermeer administrator role, including through the native async, sync, batch and +OLTP task APIs. Direct Store scans do not enforce the caller's HugeGraph graph permissions; a Vermeer +space token alone does not authorize a source graph. This restriction applies +with or without deployment credential files, including credentials configured +only on Workers. It is checked before task creation and again before execution +using the recorded task creator and the reload's effective loading type. Existing +non-admin HugeGraph load/reload tasks are rejected when started; administrators +must resubmit them. CSV loads and computation on admitted graphs retain their +existing access rules. + +Master `auth=none` retains its existing trust-all API behavior, including admin +APIs, and provides no caller-level source authorization. Use it only with trusted +callers; use `auth=token` and administrator tokens when source access must be restricted. + The current PD gRPC transport is plaintext. Keep these connections on a trusted private network or loopback; deployments crossing untrusted networks must provide transport encryption. The password file configures request credentials, not TLS. diff --git a/vermeer/apps/common/pd_peers.go b/vermeer/apps/common/pd_peers.go index a2a48daf3..3812c3f8b 100644 --- a/vermeer/apps/common/pd_peers.go +++ b/vermeer/apps/common/pd_peers.go @@ -24,6 +24,8 @@ import ( "os" "slices" "strconv" + + "github.com/sirupsen/logrus" ) func deploymentConfig(environment, key string) string { @@ -55,6 +57,12 @@ func resolvePDPeers(taskPeers []string) ([]string, error) { return nil, errors.New("pd_peers must contain valid host:port addresses") } } + for _, peer := range taskPeers { + if !slices.Contains(peers, peer) { + logrus.Warn("configured pd_peers override task PD peers") + break + } + } return peers, nil } diff --git a/vermeer/apps/common/pd_peers_test.go b/vermeer/apps/common/pd_peers_test.go index d9ad25f37..a79e559c1 100644 --- a/vermeer/apps/common/pd_peers_test.go +++ b/vermeer/apps/common/pd_peers_test.go @@ -25,8 +25,52 @@ import ( "strings" "sync/atomic" "testing" + + "github.com/sirupsen/logrus" + logtest "github.com/sirupsen/logrus/hooks/test" ) +func TestPDPeersOverrideWarning(t *testing.T) { + isolatePDPeerConfig(t) + logger := logrus.StandardLogger() + previousHooks := logger.ReplaceHooks(make(logrus.LevelHooks)) + previousLevel := logger.GetLevel() + logger.SetLevel(logrus.WarnLevel) + hook := logtest.NewLocal(logger) + t.Cleanup(func() { logger.ReplaceHooks(previousHooks); logger.SetLevel(previousLevel) }) + configured := []string{"deployment-a:8686", "deployment-b:8686"} + t.Setenv("VERMEER_PD_PEERS", `["deployment-a:8686","deployment-b:8686"]`) + for _, peers := range [][]string{nil, {configured[1], configured[0]}, {configured[0]}} { + if _, err := resolvePDPeers(peers); err != nil { + t.Fatal(err) + } + } + if err := requireTrustedPD(configured[0]); err != nil { + t.Fatal(err) + } + if len(hook.AllEntries()) != 0 { + t.Fatal("configured task peers and internal trust checks must remain quiet") + } + taskPeers := []string{"sensitive-task-a:8686", "sensitive-task-b:8686"} + peers, err := resolvePDPeers(taskPeers) + if err != nil || !reflect.DeepEqual(peers, configured) { + t.Fatal("warning must preserve deployment precedence") + } + entries := hook.AllEntries() + if len(entries) != 1 || entries[0].Level != logrus.WarnLevel { + t.Fatalf("expected one override warning, got %d entries", len(entries)) + } + entry, err := entries[0].String() + if err != nil { + t.Fatal(err) + } + for _, target := range append(taskPeers, configured...) { + if strings.Contains(entry, target) { + t.Fatal("override warning exposed a target") + } + } +} + func isolatePDPeerConfig(t *testing.T) { t.Helper() for _, key := range []string{"pd_peers", "pd_password_file", "hugegraph_credentials_file"} { diff --git a/vermeer/apps/master/bl/load_authorization_test.go b/vermeer/apps/master/bl/load_authorization_test.go new file mode 100644 index 000000000..47f584b44 --- /dev/null +++ b/vermeer/apps/master/bl/load_authorization_test.go @@ -0,0 +1,165 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bl + +import ( + "testing" + "vermeer/apps/common" + "vermeer/apps/structure" + + "github.com/prometheus/client_golang/prometheus" +) + +func isLoadPermissionError(err error) bool { + return err != nil && err.Error() == "admin permission is required to load from HugeGraph" +} + +func setLoadAuthorizationMode(t *testing.T, mode string) { + t.Helper() + previous := common.GetConfigDefault("auth", "none") + common.SetConfig("auth", mode) + t.Cleanup(func() { common.SetConfig("auth", previous) }) +} + +func TestHugegraphLoadAuthorization(t *testing.T) { + setLoadAuthorizationMode(t, "token") + previousGraphs := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_load_authorization_graphs"}, nil) + graphMgr.Init() + taskMgr.Init() + t.Cleanup(func() { + graphMgr.Init() + taskMgr.Init() + common.PrometheusMetrics.GraphCnt = previousGraphs + }) + graph := graphMgr.CreateGraph("own-space", "alias") + graph.Params = map[string]string{"load.type": "hugegraph", "load.hugegraph_name": "source/graph/g"} + if err := graphMgr.AddGraph(graph); err != nil { + t.Fatal(err) + } + for _, test := range []struct { + name, user, taskType string + params map[string]string + denied bool + }{ + {"user same-space source", "reader", "load", map[string]string{ + "load.type": "hugegraph", "load.hugegraph_name": "own-space/graph/g"}, true}, + {"user other-space source", "reader", "load", map[string]string{ + "load.type": "hugegraph", "load.hugegraph_name": "other-space/graph/g"}, true}, + {"caller params cannot set identity", "reader", "load", map[string]string{ + "load.type": "hugegraph", "create_user": "admin", "user": "admin"}, true}, + {"default user", structure.DefaultUserName, "load", map[string]string{"load.type": "hugegraph"}, true}, + {"admin load", "admin", "load", map[string]string{"load.type": "hugegraph"}, false}, + {"user explicit reload", "reader", "reload", map[string]string{"load.type": "hugegraph"}, true}, + {"user inherited reload", "reader", "reload", nil, true}, + {"admin inherited reload", "admin", "reload", nil, false}, + {"user CSV load", "reader", "load", map[string]string{"load.type": "csv"}, false}, + {"user CSV reload override", "reader", "reload", map[string]string{"load.type": "csv"}, false}, + {"user compute", "reader", "compute", map[string]string{"load.type": "hugegraph"}, false}, + } { + t.Run(test.name, func(t *testing.T) { + cred, err := structure.NewCredential(test.user, "own-space") + if err != nil { + t.Fatal(err) + } + creator, err := NewTaskCreator(cred, test.taskType) + if err != nil { + t.Fatal(err) + } + // This is the real creation preview used before duplicate checks, graph changes or persistence. + task, err := creator.NewTaskInfo("alias", test.params, -1) + if test.denied { + if !isLoadPermissionError(err) || task != nil { + t.Fatalf("unauthorized creation returned task=%v, err=%v", task, err) + } + // Both ordinary and batch admission must reject before graph/task side effects. + for _, checked := range []bool{IsCheck, NoCheck} { + if _, err := (&TaskBl{Cred: cred}).CreateTaskInfo( + "alias", test.taskType, test.params, checked); !isLoadPermissionError(err) { + t.Fatalf("checked=%v: unexpected admission error %v", checked, err) + } + } + } else if err != nil || task.CreateUser != test.user || task.SpaceName != "own-space" { + t.Fatalf("allowed creation lost authenticated identity: task=%v, err=%v", task, err) + } + + // Restored and cron-copied tasks must be checked without relying on a live HTTP credential. + restored := &structure.TaskInfo{CreateUser: test.user, SpaceName: "own-space", + GraphName: "alias", Type: test.taskType, Params: test.params} + starter, err := NewTaskStarter(restored, "$") + if test.denied { + if !isLoadPermissionError(err) || starter != nil { + t.Fatalf("unauthorized execution returned starter=%v, err=%v", starter, err) + } + } else if err != nil || starter == nil { + t.Fatalf("allowed execution rejected: %v", err) + } + }) + } + if len(taskMgr.GetAllTasks(100)) != 0 || graphMgr.GetGraphByName("own-space", "alias") != graph { + t.Fatal("rejected requests changed task or graph state") + } +} + +func TestHugegraphLoadAuthorizationRechecksReload(t *testing.T) { + setLoadAuthorizationMode(t, "token") + graphMgr.Init() + t.Cleanup(graphMgr.Init) + previousGraphs := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_reload_authorization_graphs"}, nil) + t.Cleanup(func() { common.PrometheusMetrics.GraphCnt = previousGraphs }) + graph := graphMgr.CreateGraph("own-space", "alias") + graph.Params = map[string]string{"load.type": "csv"} + if err := graphMgr.AddGraph(graph); err != nil { + t.Fatal(err) + } + cred, _ := structure.NewCredential("reader", "own-space") + creator, _ := NewTaskCreator(cred, structure.TaskTypeReload) + task, err := creator.NewTaskInfo("alias", nil, -1) + if err != nil { + t.Fatal(err) + } + // A queued reload inherits the graph parameters at execution time. + graph.Params = map[string]string{"load.type": "hugegraph"} + if _, err := NewTaskStarter(task, "$"); !isLoadPermissionError(err) { + t.Fatalf("queued reload inherited a privileged source without rejection: %v", err) + } +} + +func TestHugegraphLoadAuthorizationWithoutTokenAuth(t *testing.T) { + setLoadAuthorizationMode(t, "none") + taskMgr.Init() + t.Cleanup(taskMgr.Init) + creator, err := NewTaskCreator(structure.DefaultCredential(), structure.TaskTypeLoad) + if err != nil { + t.Fatal(err) + } + task, err := creator.NewTaskInfo("graph", map[string]string{"load.type": "hugegraph"}, -1) + if err != nil { + t.Fatalf("no-auth deployments must retain their existing trusted-caller behavior: %v", err) + } + if _, err := NewTaskStarter(task, "$"); err != nil { + t.Fatal(err) + } + common.SetConfig("auth", "token") + if _, err := NewTaskStarter(task, "$"); !isLoadPermissionError(err) { + t.Fatalf("enabling token auth must reject an old non-admin load: %v", err) + } +} diff --git a/vermeer/apps/master/bl/task_creator.go b/vermeer/apps/master/bl/task_creator.go index a7353c85b..dd49b1f4b 100644 --- a/vermeer/apps/master/bl/task_creator.go +++ b/vermeer/apps/master/bl/task_creator.go @@ -18,9 +18,13 @@ under the License. package bl import ( + "errors" "fmt" + "vermeer/apps/common" + "vermeer/apps/graphio" . "vermeer/apps/master/graphs" "vermeer/apps/master/tasks" + "vermeer/apps/options" "vermeer/apps/structure" "github.com/sirupsen/logrus" @@ -32,6 +36,32 @@ const ( NoCheck = false ) +var errHugegraphLoadPermission = errors.New("admin permission is required to load from HugeGraph") + +// Store bulk scans do not enforce the caller's HugeGraph permissions. With token +// authentication, only administrators may initiate them, regardless of where credentials are configured. +func checkHugegraphLoadPermission(user, space, graphName, taskType string, params map[string]string) error { + if common.GetConfigDefault("auth", "none").(string) != "token" { + return nil // The existing no-auth mode trusts every API caller, including admin APIs. + } + if taskType != structure.TaskTypeLoad && taskType != structure.TaskTypeReload { + return nil + } + loadType, specified := params["load.type"] + if taskType == structure.TaskTypeReload && !specified { + if graph := graphMgr.GetGraphByName(space, graphName); graph != nil { + loadType = options.GetString(graph.Params, "load.type") + } + } + if loadType == graphio.LoadTypeHugegraph { + cred, err := structure.NewCredential(user, space) + if err != nil || !cred.IsAdmin() { + return errHugegraphLoadPermission + } + } + return nil +} + // TaskCreator Create a new TaskInfo. type TaskCreator interface { // CreateTaskInfo creates a new TaskInfo from the given task description and adds it to the TaskManager. @@ -87,6 +117,9 @@ func (bc *baseCreator) createTaskInfo(graphName string, params map[string]string } func (bc *baseCreator) NewTaskInfo(graphName string, params map[string]string, taskId int32) (*structure.TaskInfo, error) { + if err := checkHugegraphLoadPermission(bc.cred.User(), bc.cred.Space(), graphName, bc.taskType, params); err != nil { + return nil, err + } task, err := taskMgr.CreateTask(bc.cred.Space(), bc.taskType, taskId) if err != nil { return nil, err diff --git a/vermeer/apps/master/bl/task_starter.go b/vermeer/apps/master/bl/task_starter.go index 33ba596be..da2ccc4f1 100644 --- a/vermeer/apps/master/bl/task_starter.go +++ b/vermeer/apps/master/bl/task_starter.go @@ -38,6 +38,11 @@ func NewTaskStarter(taskInfo *structure.TaskInfo, workerGroup string) (TaskStart if taskInfo == nil { return nil, fmt.Errorf("the argument `taskInfo` should not be nil") } + // Recheck persisted/queued tasks against the current reload source before any graph mutation or scan. + if err := checkHugegraphLoadPermission(taskInfo.CreateUser, taskInfo.SpaceName, + taskInfo.GraphName, taskInfo.Type, taskInfo.Params); err != nil { + return nil, err + } base := baseStarter{task: taskInfo, workerGroup: workerGroup} switch taskInfo.Type { case structure.TaskTypeLoad: From c2c18a543843885bf2126b97b83aa86ae20fe6b8 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 09:07:40 +0000 Subject: [PATCH 16/19] docs(vermeer): capture latest Hubble integration evidence --- .../2026-10-23/01-vermeer-loaded-pagerank.png | Bin 0 -> 122146 bytes .../2026-10-23/02-vermeer-tasks-success.png | Bin 0 -> 107194 bytes .../03-vermeer-pagerank-results.png | Bin 0 -> 82567 bytes .../hubble-integration/2026-10-23/README.md | 43 ++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 vermeer/docs/hubble-integration/2026-10-23/01-vermeer-loaded-pagerank.png create mode 100644 vermeer/docs/hubble-integration/2026-10-23/02-vermeer-tasks-success.png create mode 100644 vermeer/docs/hubble-integration/2026-10-23/03-vermeer-pagerank-results.png create mode 100644 vermeer/docs/hubble-integration/2026-10-23/README.md diff --git a/vermeer/docs/hubble-integration/2026-10-23/01-vermeer-loaded-pagerank.png b/vermeer/docs/hubble-integration/2026-10-23/01-vermeer-loaded-pagerank.png new file mode 100644 index 0000000000000000000000000000000000000000..53c480ea6eb98e6ba8d1af4661098fe37dbb2434 GIT binary patch literal 122146 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%0A&6rBtVdWJ6obvhZ@%;cnQt>fYE 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 literal 0 HcmV?d00001 diff --git a/vermeer/docs/hubble-integration/2026-10-23/02-vermeer-tasks-success.png b/vermeer/docs/hubble-integration/2026-10-23/02-vermeer-tasks-success.png new file mode 100644 index 0000000000000000000000000000000000000000..95a1fe3d53eb08e0d2b4c2878734a8588cf220c3 GIT binary patch literal 107194 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�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 z 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 l|Gxo?Gibde8z@|v89Z0a5x;3^5&d(r>^ literal 0 HcmV?d00001 diff --git a/vermeer/docs/hubble-integration/2026-10-23/README.md b/vermeer/docs/hubble-integration/2026-10-23/README.md new file mode 100644 index 000000000..be63a07a8 --- /dev/null +++ b/vermeer/docs/hubble-integration/2026-10-23/README.md @@ -0,0 +1,43 @@ + + +# Hubble integration evidence — 2026-10-23 + +Real Chromium screenshots from a local retained development stack: + +- Hubble: latest remote master `eef5afd29762f394667494b726afdce1b938214d`. +- Vermeer executable: `273e1ecf63bc739068ea08e4e2b9ff47334d94a5`. +- Synthetic graph: `DEFAULT/vermeer_review_20261023`, six vertices and seven directed edges. +- Existing anonymous development mode (`auth=none`); this run does not verify token authorization. + +The Hubble helper's API smoke test completed load task `-13` and 20-step PageRank task `-14`. +Chromium submitted reload task `-15` and PageRank task `-16` with the UI defaults +(10 steps, damping 0.85). Both completed successfully; the task list and full result page rendered +all six finite ranks without page script errors. Independent PageRank iteration on the fixture +matched both outputs within `3e-8` absolute error. + +1. [Loaded graph and PageRank parameters](01-vermeer-loaded-pagerank.png). + Batch results appear in Async Tasks; the interactive canvas is unused in this flow. +2. [Successful load and compute tasks](02-vermeer-tasks-success.png). +3. [Full PageRank result](03-vermeer-pagerank-results.png). + +Before validation, the retained Store exhausted its 32GB disk through 1GB Raft segments despite +its advertised 64MB option. Unused zero extents were reclaimed while Store was stopped, with all +35 segment SHA-256 checksums unchanged. A supported JRaft JVM property limited new segments to +16MB for this local environment; an actual new 16MB segment was observed. This is a local runtime +workaround. No Store binary or Toolchain source change is included here, and this run does not +claim the Store configuration propagation bug has been fixed upstream. From a2eec0c94af5de6ebe73f458980e1dd25a0e84e5 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 09:55:38 +0000 Subject: [PATCH 17/19] fix(vermeer): cancel partially dispatched compute tasks --- .github/workflows/vermeer-ci.yml | 2 +- vermeer/README.md | 8 + vermeer/apps/compute/task.go | 3 + .../apps/master/bl/compute_dispatch_test.go | 190 +++++++++++++++++ vermeer/apps/master/bl/task_starter.go | 46 +++- .../master/bl/worker_availability_test.go | 2 + vermeer/apps/worker/compute_bl.go | 32 ++- .../compute_dispatch_cancellation_test.go | 198 ++++++++++++++++++ vermeer/apps/worker/grpc_handlers.go | 15 +- 9 files changed, 488 insertions(+), 8 deletions(-) create mode 100644 vermeer/apps/master/bl/compute_dispatch_test.go create mode 100644 vermeer/apps/worker/compute_dispatch_cancellation_test.go diff --git a/.github/workflows/vermeer-ci.yml b/.github/workflows/vermeer-ci.yml index 3c08d2dd9..bc93ca4cc 100644 --- a/.github/workflows/vermeer-ci.yml +++ b/.github/workflows/vermeer-ci.yml @@ -77,7 +77,7 @@ jobs: go test -race ./apps/common go test -race ./apps/master/workers ./apps/master/services -run 'TestWorkerAvailability' go test -race ./apps/master/bl -run 'Test(WorkerAvailability|HugegraphLoadAuthorization)' - go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex)' + go test -race ./apps/worker -run 'Test(LoadReceivers|RecvVertex|ComputeDispatchCancellation)' go test -race ./apps/structure -run 'Test(VertexStore|GraphPropagates|MemoryVertex|HugegraphSchema)' - name: Build diff --git a/vermeer/README.md b/vermeer/README.md index 525e9322c..b1990d39f 100644 --- a/vermeer/README.md +++ b/vermeer/README.md @@ -389,6 +389,14 @@ A group preallocated to a graph absent from that space's Master graph metadata is not counted until the graph appears there; the scheduler can still use the allocation for that graph's first load. +If compute dispatch fails after some workers accept it, Master records the original +error and makes a best-effort cancellation request to each accepted worker's original +session, with a five-second deadline per worker. A worker that has not created the +task yet returns `NotFound`, which this rollback retries within the same deadline. +Compute initialization and the first-superstep handoff consume cancellation even +while the worker is idle; active BSP and graph preparation retain their existing +cooperative cancellation behavior. + ### Example: Run PageRank ```bash diff --git a/vermeer/apps/compute/task.go b/vermeer/apps/compute/task.go index b73cd80ef..486b7a933 100644 --- a/vermeer/apps/compute/task.go +++ b/vermeer/apps/compute/task.go @@ -46,6 +46,9 @@ type ComputerTask struct { SettingOutEdges bool SendCount map[string]*int32 RecvCount map[string]*int32 + // WaitingForStep is protected by Locker during the initialization handoff. + WaitingForStep bool + CancelOnce sync.Once } func (ct *ComputerTask) FreeMemory() { diff --git a/vermeer/apps/master/bl/compute_dispatch_test.go b/vermeer/apps/master/bl/compute_dispatch_test.go new file mode 100644 index 000000000..6029839b5 --- /dev/null +++ b/vermeer/apps/master/bl/compute_dispatch_test.go @@ -0,0 +1,190 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bl + +import ( + "context" + "errors" + "io" + "testing" + "time" + "vermeer/apps/common" + "vermeer/apps/compute" + "vermeer/apps/master/workers" + pb "vermeer/apps/protos" + "vermeer/apps/structure" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type computeDispatchStream struct { + grpc.ServerStream + requests int + sendErr error +} + +func (s *computeDispatchStream) Send(*pb.ComputeTaskResp) error { + s.requests++ + return s.sendErr +} +func (s *computeDispatchStream) Recv() (*pb.ComputeTaskReq, error) { return nil, io.EOF } + +type computeCancelClient struct { + pb.WorkerClient + task *structure.TaskInfo + calls int + notFound bool + cancelErr error + invalid bool +} + +func (c *computeCancelClient) ControlTask(ctx context.Context, req *pb.ControlTaskReq, + _ ...grpc.CallOption) (*pb.ControlTaskResp, error) { + c.calls++ + if req.TaskID != c.task.ID || req.Action != structure.ActionCancelTask { + c.invalid = true + return nil, errors.New("wrong cancellation scope") + } + if c.task.State != structure.TaskStateError { + c.invalid = true + return nil, errors.New("task was not failed before cancellation") + } + deadline, ok := ctx.Deadline() + if !ok || time.Until(deadline) > 5*time.Second { + c.invalid = true + return nil, errors.New("cancellation has no bounded deadline") + } + if c.notFound && c.calls == 1 { + return nil, status.Error(codes.NotFound, "not initialized") + } + return &pb.ControlTaskResp{}, c.cancelErr +} + +// Reuse the registration regression's isolated stores rather than reopen Pebble fixtures. +func testPartialComputeDispatch(t *testing.T) { + previousGraphs := common.PrometheusMetrics.GraphCnt + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec( + prometheus.GaugeOpts{Name: "test_compute_dispatch_graphs"}, nil) + t.Cleanup(func() { common.PrometheusMetrics.GraphCnt = previousGraphs }) + if err := graphMgr.InitStore(); err != nil { + t.Fatal(err) + } + algorithmMgr.Init() + algorithmMgr.Register(new(compute.AlgorithmMakerBase), "test") + for _, test := range []struct { + name string + action pb.ComputeAction + missingWorker, missingStream, sendFailure, cancelFailure, lateInit bool + }{ + {name: "missing_worker", missingWorker: true}, + {name: "missing_stream", missingStream: true}, + {name: "send_failure", sendFailure: true}, + {name: "cleanup_failure", missingStream: true, cancelFailure: true}, + {name: "late_initialization", missingStream: true, lateInit: true}, + {name: "success"}, + {name: "out_edges", action: pb.ComputeAction_SettingOutEdges, sendFailure: true}, + {name: "out_degree", action: pb.ComputeAction_SettingOutDegree, sendFailure: true}, + } { + t.Run(test.name, func(t *testing.T) { + ServerMgr.Init() + defer ServerMgr.Init() + task, err := taskMgr.CreateTask("compute-dispatch", structure.TaskTypeCompute, 0) + if err != nil { + t.Fatal(err) + } + task.GraphName = "graph" + graph := graphMgr.CreateGraph(task.SpaceName, task.GraphName) + clients := make([]*computeCancelClient, 3) + streams := make([]*computeDispatchStream, 3) + names := []string{"compute-first", "compute-second", "compute-third"} + dispatchErr := errors.New("compute dispatch failed") + for i, name := range names { + graph.Workers = append(graph.Workers, &structure.GraphWorker{Name: name}) + task.Workers = append(task.Workers, &structure.TaskWorker{Name: name}) + clients[i] = &computeCancelClient{task: task} + streams[i] = new(computeDispatchStream) + workerMgr.RemoveWorker(name) + if i == 2 && test.missingWorker { + continue + } + _, err = workerMgr.AddWorker(&workers.WorkerClient{Name: name, Session: clients[i]}) + if err != nil { + t.Fatal(err) + } + if i != 2 || !test.missingStream { + ServerMgr.computeSevers[name] = &ComputeTaskServer{streamServer: streams[i]} + } + } + if test.sendFailure { + streams[2].sendErr = dispatchErr + } + if test.cancelFailure { + clients[0].cancelErr = errors.New("cancel failed") + } + clients[0].notFound = test.lateInit + ct := &compute.ComputerTask{Task: task, Algorithm: "base"} + err = StartComputeTask(graph, ct, test.action) + failed := test.missingWorker || test.missingStream || test.sendFailure + if (err != nil) != failed { + t.Fatalf("dispatch err=%v, want failure=%v", err, failed) + } + if test.sendFailure && !errors.Is(err, dispatchErr) { + t.Fatalf("replaced original error: %v", err) + } + if failed && (task.State != structure.TaskStateError || task.ErrMessage != err.Error()) { + t.Errorf("partial dispatch left master state=%s, error=%q; want Error and original dispatch error", task.State, task.ErrMessage) + } else if failed { + saved, readErr := taskMgr.ReadTask(task.ID) + if readErr != nil { + t.Fatal(readErr) + } + if task.State != structure.TaskStateError || saved.State != task.State || + task.ErrMessage != err.Error() || saved.ErrMessage != err.Error() { + t.Fatal("dispatch failure must persist the original error") + } + } + for i, c := range clients { + if c.invalid { + t.Errorf("worker %d received an invalid cancellation", i) + } + want := 0 + if failed && i < 2 { + want = 1 + } + if test.lateInit && i == 0 { + want = 2 + } + if c.calls != want { + t.Errorf("worker %d cancellation calls=%d, want %d", i, c.calls, want) + } + } + for i, s := range streams { + want := 1 + if i == 2 && (test.missingWorker || test.missingStream) { + want = 0 + } + if s.requests != want { + t.Errorf("worker %d sends=%d want=%d", i, s.requests, want) + } + } + }) + } +} diff --git a/vermeer/apps/master/bl/task_starter.go b/vermeer/apps/master/bl/task_starter.go index da2ccc4f1..3fb3d4650 100644 --- a/vermeer/apps/master/bl/task_starter.go +++ b/vermeer/apps/master/bl/task_starter.go @@ -18,14 +18,19 @@ under the License. package bl import ( + "context" "fmt" + "time" "vermeer/apps/compute" . "vermeer/apps/master/graphs" "vermeer/apps/master/tasks" + "vermeer/apps/master/workers" pb "vermeer/apps/protos" "vermeer/apps/structure" "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // TaskStarter The starter of tasks @@ -277,7 +282,7 @@ func (cts *computeTaskStarter) StartTask() error { return nil } -func StartComputeTask(graph *structure.VermeerGraph, computeTask *compute.ComputerTask, action pb.ComputeAction) error { +func StartComputeTask(graph *structure.VermeerGraph, computeTask *compute.ComputerTask, action pb.ComputeAction) (err error) { var workerState structure.TaskState switch action { case pb.ComputeAction_Compute: @@ -305,6 +310,26 @@ func StartComputeTask(graph *structure.VermeerGraph, computeTask *compute.Comput workerState = structure.TaskStateSettingOutDegree } + dispatched := make([]*workers.WorkerClient, 0, len(graph.Workers)) + defer func() { + if err == nil { + return + } + taskMgr.SetError(computeTask.Task, err.Error()) + for _, worker := range dispatched { + if worker.Session == nil { + logrus.Errorf("cannot cancel partially dispatched compute task %d: worker %s session is unavailable", + computeTask.Task.ID, worker.Name) + continue + } + cancelErr := cancelComputeTask(worker.Session, computeTask.Task.ID) + if cancelErr != nil { + logrus.Errorf("failed to cancel partially dispatched compute task %d on worker %s: %v", + computeTask.Task.ID, worker.Name, cancelErr) + } + } + }() + for _, w := range graph.Workers { wc := workerMgr.GetWorker(w.Name) if wc == nil { @@ -329,6 +354,25 @@ func StartComputeTask(graph *structure.VermeerGraph, computeTask *compute.Comput if err != nil { return err } + dispatched = append(dispatched, wc) } return nil } + +func cancelComputeTask(session pb.WorkerClient, taskID int32) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + req := &pb.ControlTaskReq{TaskID: taskID, Action: structure.ActionCancelTask} + for { + _, err := session.ControlTask(ctx, req) + if status.Code(err) != codes.NotFound { + return err + } + // A successful stream send can precede task creation on the worker. + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(50 * time.Millisecond): + } + } +} diff --git a/vermeer/apps/master/bl/worker_availability_test.go b/vermeer/apps/master/bl/worker_availability_test.go index 579263d41..7a46337b2 100644 --- a/vermeer/apps/master/bl/worker_availability_test.go +++ b/vermeer/apps/master/bl/worker_availability_test.go @@ -394,4 +394,6 @@ func TestWorkerAvailabilityWaitsForRegistration(t *testing.T) { } }) } + + t.Run("compute_dispatch", testPartialComputeDispatch) } diff --git a/vermeer/apps/worker/compute_bl.go b/vermeer/apps/worker/compute_bl.go index 2e5a5cb50..14a1fc2a8 100644 --- a/vermeer/apps/worker/compute_bl.go +++ b/vermeer/apps/worker/compute_bl.go @@ -117,7 +117,14 @@ func (cb *ComputeBl) StartCompute(taskID int32, spaceName string, graphName stri return } + ct.Locker.Lock() + if !cb.CheckAction(ct) { + ct.Locker.Unlock() + return + } ct.Task.SetState(structure.TaskStateInitOK) + ct.WaitingForStep = true + ct.Locker.Unlock() req := pb.ComputeTaskStatusReq{ TaskId: taskID, State: string(structure.TaskStateInitOK), @@ -127,8 +134,14 @@ func (cb *ComputeBl) StartCompute(taskID int32, spaceName string, graphName stri } _, err = ServiceWorker.MasterClient.ComputeTaskStatus(context.Background(), &req) if err != nil { - logrus.Errorf("ComputeTaskStatus error:%v", err) - cb.SetStatusError(taskID, fmt.Sprintf("ComputeTaskStatus error:%v", err)) + ct.Locker.Lock() + ct.WaitingForStep = false + isContinue := cb.CheckAction(ct) + ct.Locker.Unlock() + if isContinue { + logrus.Errorf("ComputeTaskStatus error:%v", err) + cb.SetStatusError(taskID, fmt.Sprintf("ComputeTaskStatus error:%v", err)) + } } //go cb.RunSuperStep(ct.Task.ID, nil) @@ -144,9 +157,16 @@ func (cb *ComputeBl) RunSuperStep(taskID int32, computeValues map[string][]byte) } }() computeTask := ComputeTaskMgr.GetTask(taskID) + if computeTask == nil { + return + } + computeTask.Locker.Lock() + computeTask.WaitingForStep = false if !cb.CheckAction(computeTask) { + computeTask.Locker.Unlock() return } + computeTask.Locker.Unlock() computeTask.Step += 1 ctx := computeTask.ComputeWorker.Context() ctx.Step += 1 @@ -764,9 +784,11 @@ func (cb *ComputeBl) CheckAction(computeTask *compute.ComputerTask) (isContinue } func (cb *ComputeBl) cancelAction(computeTask *compute.ComputerTask) { - computeTask.Task.SetState(structure.TaskStateCanceled) - common.PrometheusMetrics.TaskRunningCnt.WithLabelValues(computeTask.Task.Type).Dec() - time.AfterFunc(1*time.Minute, func() { cb.endTask(computeTask.Task.ID) }) + computeTask.CancelOnce.Do(func() { + computeTask.Task.SetState(structure.TaskStateCanceled) + common.PrometheusMetrics.TaskRunningCnt.WithLabelValues(computeTask.Task.Type).Dec() + time.AfterFunc(1*time.Minute, func() { cb.endTask(computeTask.Task.ID) }) + }) } func (cb *ComputeBl) pauseAction(computeTask *compute.ComputerTask) bool { diff --git a/vermeer/apps/worker/compute_dispatch_cancellation_test.go b/vermeer/apps/worker/compute_dispatch_cancellation_test.go new file mode 100644 index 000000000..de1193b99 --- /dev/null +++ b/vermeer/apps/worker/compute_dispatch_cancellation_test.go @@ -0,0 +1,198 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to You under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package worker + +import ( + "context" + "errors" + "testing" + "time" + "vermeer/apps/common" + "vermeer/apps/compute" + "vermeer/apps/options" + pb "vermeer/apps/protos" + "vermeer/apps/structure" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type computeStatusClient struct { + pb.MasterClient + calls int + entered chan struct{} + release chan struct{} + reportErr error +} + +func (c *computeStatusClient) ComputeTaskStatus(context.Context, *pb.ComputeTaskStatusReq, + ...grpc.CallOption) (*pb.ComputeTaskStatusResp, error) { + c.calls++ + if c.entered != nil && c.calls == 1 { + close(c.entered) + <-c.release + } + return &pb.ComputeTaskStatusResp{}, c.reportErr +} + +type cancelInitWorker struct { + compute.WorkerComputerBase + entered chan struct{} + release chan struct{} +} + +func (w *cancelInitWorker) Init() error { + close(w.entered) + <-w.release + return nil +} + +type cancelInitMaker struct { + compute.AlgorithmMakerBase + worker *cancelInitWorker +} + +func (m *cancelInitMaker) CreateWorkerComputer() compute.WorkerComputer { return m.worker } + +func TestComputeDispatchCancellation(t *testing.T) { + previousOptions := options.AllOptionsMap + previousGraphs := common.PrometheusMetrics.GraphCnt + previousTasks := common.PrometheusMetrics.TaskCnt + previousRunning := common.PrometheusMetrics.TaskRunningCnt + previousMaster := ServiceWorker.MasterClient + options.Init() + options.AllOptionsMap["compute.algorithm"] = &options.Option{ + Name: "compute.algorithm", ValueType: options.OptionValueTypeString, Value: "base", Convert: options.ToString, + } + common.PrometheusMetrics.GraphCnt = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "test_compute_cancel_graphs"}, nil) + common.PrometheusMetrics.TaskCnt = prometheus.NewCounterVec(prometheus.CounterOpts{Name: "test_compute_cancel_tasks"}, []string{"type"}) + common.PrometheusMetrics.TaskRunningCnt = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "test_compute_cancel_running"}, []string{"type"}) + GraphMgr.Init() + TaskMgr.Init() + ComputeTaskMgr.Init() + AlgorithmMgr.Init() + t.Cleanup(func() { + GraphMgr.Init() + TaskMgr.Init() + ComputeTaskMgr.Init() + options.AllOptionsMap = previousOptions + common.PrometheusMetrics.GraphCnt = previousGraphs + common.PrometheusMetrics.TaskCnt = previousTasks + common.PrometheusMetrics.TaskRunningCnt = previousRunning + ServiceWorker.MasterClient = previousMaster + }) + graph := GraphMgr.CreateGraph("compute-cancel", "graph") + graph.Workers = []*structure.GraphWorker{{Name: "worker", IsSelf: true}} + if err := GraphMgr.AddGraph(graph); err != nil { + t.Fatal(err) + } + handler := new(PeerHandler) + t.Run("before_task_creation", func(t *testing.T) { + _, err := handler.ControlTask(context.Background(), &pb.ControlTaskReq{ + TaskID: 7001, Action: structure.ActionCancelTask}) + if status.Code(err) != codes.NotFound { + t.Fatalf("missing task cancellation must be retryable NotFound, got %v", err) + } + }) + for index := 0; index < 3; index++ { + duringInit := index == 0 + name := "waiting_for_first_step" + if duringInit { + name = "during_initialization" + } else if index == 2 { + name = "status_error_after_cancel" + } + t.Run(name, func(t *testing.T) { + taskID := int32(7002 + index) + statusClient := new(computeStatusClient) + if index == 2 { + statusClient.entered = make(chan struct{}) + statusClient.release = make(chan struct{}) + statusClient.reportErr = errors.New("status report failed") + } + ServiceWorker.MasterClient = statusClient + initWorker := &cancelInitWorker{entered: make(chan struct{}), release: make(chan struct{})} + if duringInit { + AlgorithmMgr.Register(&cancelInitMaker{worker: initWorker}, "test") + } else { + AlgorithmMgr.Register(new(compute.AlgorithmMakerBase), "test") + } + done := make(chan struct{}) + go func() { + defer close(done) + new(ComputeBl).StartCompute(taskID, graph.SpaceName, graph.Name, "base", map[string]string{"compute.parallel": "1"}) + }() + if duringInit { + select { + case <-initWorker.entered: + case <-time.After(time.Second): + t.Fatal("worker did not reach initialization") + } + } else if index == 2 { + select { + case <-statusClient.entered: + case <-time.After(time.Second): + t.Fatal("status report was not reached") + } + } else { + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("initialization did not finish") + } + } + _, err := handler.ControlTask(context.Background(), &pb.ControlTaskReq{ + TaskID: taskID, Action: structure.ActionCancelTask}) + if err != nil { + t.Fatal(err) + } + if duringInit { + close(initWorker.release) + } else if index == 2 { + close(statusClient.release) + } + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("canceled initialization did not finish") + } + ct := ComputeTaskMgr.GetTask(taskID) + if ct == nil || ct.Task.State != structure.TaskStateCanceled { + t.Fatalf("cancellation was not consumed: %+v", ct) + } + wantReports := 1 + if duringInit { + wantReports = 0 + } + if statusClient.calls != wantReports { + t.Errorf("status reports=%d want=%d", statusClient.calls, wantReports) + } + // An already queued first-step message must not consume cancellation twice. + new(ComputeBl).RunSuperStep(taskID, nil) + if ct.Task.State != structure.TaskStateCanceled { + t.Fatal("late first step resumed a canceled task") + } + if running := testutil.ToFloat64(common.PrometheusMetrics.TaskRunningCnt.WithLabelValues(structure.TaskTypeCompute)); running != 0 { + t.Fatalf("cancellation left running gauge=%v", running) + } + }) + } +} diff --git a/vermeer/apps/worker/grpc_handlers.go b/vermeer/apps/worker/grpc_handlers.go index 6cd4fe17a..87d38b05d 100644 --- a/vermeer/apps/worker/grpc_handlers.go +++ b/vermeer/apps/worker/grpc_handlers.go @@ -26,10 +26,13 @@ import ( "vermeer/apps/common" "vermeer/apps/graphio" pb "vermeer/apps/protos" + "vermeer/apps/structure" "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" ) type PeerHandler struct { @@ -155,12 +158,22 @@ func (ph *PeerHandler) ControlTask(ctx context.Context, req *pb.ControlTaskReq) _ = ctx task := TaskMgr.GetTaskByID(req.GetTaskID()) if task == nil { - return &pb.ControlTaskResp{}, nil + return nil, status.Error(codes.NotFound, "task is not initialized") } err := TaskMgr.SetAction(task, req.Action) if err != nil { return &pb.ControlTaskResp{}, err } + if req.Action == structure.ActionCancelTask && task.Type == structure.TaskTypeCompute { + if computeTask := ComputeTaskMgr.GetTask(task.ID); computeTask != nil { + computeTask.Locker.Lock() + if computeTask.WaitingForStep { + computeTask.WaitingForStep = false + new(ComputeBl).CheckAction(computeTask) + } + computeTask.Locker.Unlock() + } + } //SELECT: // for { // switch task.State { From 0d09ba088f84b5466ba3984413f5e4a4c0dd4f78 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 11:42:09 +0000 Subject: [PATCH 18/19] ci: clear required ASF status checks --- .asf.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 21ee08f7a..fabd675fa 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -38,10 +38,7 @@ github: # strict means "Require branches to be up-to-date before merging". strict: false # contexts are the names of checks that must pass - contexts: - #- computer-ci # always stuck in CI - - check-license-header - - Analyze (java) + contexts: [] required_pull_request_reviews: dismiss_stale_reviews: true require_code_owner_reviews: false From 241a6a5a476ccbbb1f7941bdebe7225d6afc4d65 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 23 Oct 2026 12:41:59 +0000 Subject: [PATCH 19/19] fix(vermeer): release task lock while paused --- vermeer/apps/worker/compute_bl.go | 30 +++-- .../compute_dispatch_cancellation_test.go | 119 ++++++++++++++++++ 2 files changed, 139 insertions(+), 10 deletions(-) diff --git a/vermeer/apps/worker/compute_bl.go b/vermeer/apps/worker/compute_bl.go index 14a1fc2a8..bbfa74e82 100644 --- a/vermeer/apps/worker/compute_bl.go +++ b/vermeer/apps/worker/compute_bl.go @@ -117,14 +117,26 @@ func (cb *ComputeBl) StartCompute(taskID int32, spaceName string, graphName stri return } - ct.Locker.Lock() - if !cb.CheckAction(ct) { + for { + ct.Locker.Lock() + action := atomic.LoadInt32(&ct.Task.Action) + if action != structure.ActionPauseTask { + if action == structure.ActionCancelTask { + cb.cancelAction(ct) + ct.Locker.Unlock() + return + } + ct.Task.SetState(structure.TaskStateInitOK) + ct.WaitingForStep = true + ct.Locker.Unlock() + break + } + // Pause outside the handoff lock, then recheck before publishing readiness. ct.Locker.Unlock() - return + if !cb.pauseAction(ct) { + return + } } - ct.Task.SetState(structure.TaskStateInitOK) - ct.WaitingForStep = true - ct.Locker.Unlock() req := pb.ComputeTaskStatusReq{ TaskId: taskID, State: string(structure.TaskStateInitOK), @@ -136,9 +148,8 @@ func (cb *ComputeBl) StartCompute(taskID int32, spaceName string, graphName stri if err != nil { ct.Locker.Lock() ct.WaitingForStep = false - isContinue := cb.CheckAction(ct) ct.Locker.Unlock() - if isContinue { + if cb.CheckAction(ct) { logrus.Errorf("ComputeTaskStatus error:%v", err) cb.SetStatusError(taskID, fmt.Sprintf("ComputeTaskStatus error:%v", err)) } @@ -162,11 +173,10 @@ func (cb *ComputeBl) RunSuperStep(taskID int32, computeValues map[string][]byte) } computeTask.Locker.Lock() computeTask.WaitingForStep = false + computeTask.Locker.Unlock() if !cb.CheckAction(computeTask) { - computeTask.Locker.Unlock() return } - computeTask.Locker.Unlock() computeTask.Step += 1 ctx := computeTask.ComputeWorker.Context() ctx.Step += 1 diff --git a/vermeer/apps/worker/compute_dispatch_cancellation_test.go b/vermeer/apps/worker/compute_dispatch_cancellation_test.go index de1193b99..0eabf0cd6 100644 --- a/vermeer/apps/worker/compute_dispatch_cancellation_test.go +++ b/vermeer/apps/worker/compute_dispatch_cancellation_test.go @@ -18,8 +18,10 @@ limitations under the License. package worker import ( + "bytes" "context" "errors" + "runtime" "testing" "time" "vermeer/apps/common" @@ -195,4 +197,121 @@ func TestComputeDispatchCancellation(t *testing.T) { } }) } + for index, phase := range []string{"initialization", "first_step", "status_error"} { + t.Run("paused_"+phase, func(t *testing.T) { + taskID := int32(7010 + index) + statusClient := new(computeStatusClient) + if phase == "status_error" { + statusClient.entered = make(chan struct{}) + statusClient.release = make(chan struct{}) + statusClient.reportErr = errors.New("status report failed") + } + ServiceWorker.MasterClient = statusClient + initWorker := &cancelInitWorker{entered: make(chan struct{}), release: make(chan struct{})} + if phase == "initialization" { + AlgorithmMgr.Register(&cancelInitMaker{worker: initWorker}, "test") + } else { + AlgorithmMgr.Register(new(compute.AlgorithmMakerBase), "test") + } + start := func() { + new(ComputeBl).StartCompute(taskID, graph.SpaceName, graph.Name, "base", + map[string]string{"compute.parallel": "1"}) + } + done := make(chan struct{}) + var entered <-chan struct{} + if phase == "initialization" { + entered = initWorker.entered + } else if phase == "status_error" { + entered = statusClient.entered + } + if entered == nil { + start() + } else { + go func() { defer close(done); start() }() + select { + case <-entered: + case <-time.After(time.Second): + t.Fatal("compute did not reach the controlled initialization gate") + } + } + if _, err := handler.ControlTask(context.Background(), &pb.ControlTaskReq{ + TaskID: taskID, Action: structure.ActionPauseTask}); err != nil { + t.Fatal(err) + } + switch phase { + case "initialization": + close(initWorker.release) + case "status_error": + close(statusClient.release) + default: + go func() { defer close(done); new(ComputeBl).RunSuperStep(taskID, nil) }() + } + // Observe the actual pause wait before canceling, without changing its 10-second cadence. + if !waitForComputePause() { + t.Error("compute did not enter the pause wait") + } + canceled := make(chan error, 1) + go func() { + _, err := handler.ControlTask(context.Background(), &pb.ControlTaskReq{ + TaskID: taskID, Action: structure.ActionCancelTask}) + canceled <- err + }() + select { + case err := <-canceled: + if err != nil { + t.Error(err) + } + case <-time.After(time.Second): + t.Error("cancel RPC blocked behind the paused computation") + // Join the old-source negative control too, before restoring shared fixtures. + select { + case <-canceled: + case <-time.After(11 * time.Second): + t.Fatal("cancel RPC did not finish after the pause poll") + } + } + select { + case <-done: + case <-time.After(11 * time.Second): + t.Fatal("paused computation did not consume cancellation") + } + ct := ComputeTaskMgr.GetTask(taskID) + if ct.Task.State != structure.TaskStateCanceled { + t.Fatalf("paused computation ended in state %s", ct.Task.State) + } + wantReports := 1 + if phase == "initialization" { + wantReports = 0 + } + if statusClient.calls != wantReports { + t.Errorf("status reports=%d want=%d", statusClient.calls, wantReports) + } + new(ComputeBl).RunSuperStep(taskID, nil) + if ct.Task.State != structure.TaskStateCanceled { + t.Error("late first step resumed a canceled paused task") + } + if running := testutil.ToFloat64(common.PrometheusMetrics.TaskRunningCnt.WithLabelValues(structure.TaskTypeCompute)); running != 0 { + t.Errorf("paused cancellation left running gauge=%v", running) + } + }) + } +} + +func waitForComputePause() bool { + stack := make([]byte, 128<<10) + deadline := time.NewTimer(time.Second) + defer deadline.Stop() + tick := time.NewTicker(time.Millisecond) + defer tick.Stop() + for { + n := runtime.Stack(stack, true) + if bytes.Contains(stack[:n], []byte("vermeer/apps/worker.(*ComputeBl).pauseAction(")) { + return true + } + select { + case <-deadline.C: + return false + case <-tick.C: + } + } }