github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/servermaster/scheduler/resource_filter.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package scheduler 15 16 import ( 17 "context" 18 19 "github.com/pingcap/log" 20 "github.com/pingcap/tiflow/engine/model" 21 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 22 schedModel "github.com/pingcap/tiflow/engine/servermaster/scheduler/model" 23 "github.com/pingcap/tiflow/pkg/errors" 24 "go.uber.org/zap" 25 "golang.org/x/exp/slices" 26 ) 27 28 // PlacementConstrainer describes an object that provides 29 // the placement constraint for an external resource. 30 type PlacementConstrainer interface { 31 GetPlacementConstraint( 32 ctx context.Context, 33 resourceKey resModel.ResourceKey, 34 ) (resModel.ExecutorID, bool, error) 35 } 36 37 // MockPlacementConstrainer uses a resource executor binding map to implement PlacementConstrainer 38 type MockPlacementConstrainer struct { 39 ResourceList map[resModel.ResourceKey]model.ExecutorID 40 } 41 42 // GetPlacementConstraint implements PlacementConstrainer.GetPlacementConstraint 43 func (c *MockPlacementConstrainer) GetPlacementConstraint( 44 _ context.Context, 45 resourceKey resModel.ResourceKey, 46 ) (resModel.ExecutorID, bool, error) { 47 executorID, exists := c.ResourceList[resourceKey] 48 if !exists { 49 return "", false, errors.ErrResourceDoesNotExist.GenWithStackByArgs(resourceKey.ID) 50 } 51 if executorID == "" { 52 return "", false, nil 53 } 54 return executorID, true, nil 55 } 56 57 // resourceFilter filters candidate executors by 58 // the request's resource requirements. 59 type resourceFilter struct { 60 constrainer PlacementConstrainer 61 } 62 63 func newResourceFilter(constrainer PlacementConstrainer) *resourceFilter { 64 return &resourceFilter{constrainer: constrainer} 65 } 66 67 const ( 68 noExecutorConstraint = "" 69 ) 70 71 // getExecutor locates one unique executor onto which the task should be scheduled. 72 // If no placement requirement is found, noExecutorConstraint is returned. 73 func (f *resourceFilter) getExecutor( 74 ctx context.Context, 75 request *schedModel.SchedulerRequest, 76 ) (model.ExecutorID, error) { 77 var ( 78 lastResourceID resModel.ResourceID 79 ret model.ExecutorID 80 ) 81 82 ret = noExecutorConstraint 83 for _, resource := range request.ExternalResources { 84 resourceID := resource.ID 85 // GetPlacementConstraint() may incur database queries. 86 // Note the performance. 87 executorID, hasConstraint, err := f.constrainer.GetPlacementConstraint(ctx, resource) 88 if err != nil { 89 return noExecutorConstraint, err 90 } 91 if !hasConstraint { 92 // The resource requires no special placement. 93 log.Debug("No constraint is found for resource", 94 zap.String("resource-id", resourceID)) 95 continue 96 } 97 log.Info("Found resource constraint for resource", 98 zap.String("resource-id", resourceID), 99 zap.String("executor-id", string(executorID))) 100 101 if ret != noExecutorConstraint && ret != executorID { 102 // Conflicting constraints. 103 // We are forced to schedule the task to 104 // two different executors, which is impossible. 105 log.Warn("Conflicting resource constraints", 106 zap.Any("resources", request.ExternalResources)) 107 return noExecutorConstraint, errors.ErrResourceConflict. 108 GenWithStackByArgs(resourceID, lastResourceID, ret, executorID) 109 } 110 ret = executorID 111 lastResourceID = resourceID 112 } 113 return ret, nil 114 } 115 116 func (f *resourceFilter) GetEligibleExecutors( 117 ctx context.Context, 118 request *schedModel.SchedulerRequest, 119 candidates []model.ExecutorID, 120 ) ([]model.ExecutorID, error) { 121 if len(request.ExternalResources) == 0 { 122 return candidates, nil 123 } 124 125 finalCandidate, err := f.getExecutor(ctx, request) 126 if err != nil { 127 return nil, err 128 } 129 130 if finalCandidate == noExecutorConstraint { 131 // Any executor is good. 132 return candidates, nil 133 } 134 135 // Check that finalCandidate is found in the input candidates. 136 if slices.Index(candidates, finalCandidate) == -1 { 137 return nil, errors.ErrFilterNoResult.GenWithStackByArgs("resource") 138 } 139 return []model.ExecutorID{finalCandidate}, nil 140 }