volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/extender/extender.go (about) 1 /* 2 Copyright 2022 The Volcano Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package extender 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net/http" 25 "strings" 26 "time" 27 28 "k8s.io/klog/v2" 29 30 "volcano.sh/volcano/pkg/scheduler/api" 31 "volcano.sh/volcano/pkg/scheduler/framework" 32 "volcano.sh/volcano/pkg/scheduler/plugins/util" 33 ) 34 35 const ( 36 // PluginName indicates name of volcano scheduler plugin. 37 PluginName = "extender" 38 39 // ExtenderURLPrefix is the key for providing extender endpoint address 40 ExtenderURLPrefix = "extender.urlPrefix" 41 // ExtenderHTTPTimeout is the timeout for extender http calls 42 ExtenderHTTPTimeout = "extender.httpTimeout" 43 // ExtenderOnSessionOpenVerb is the verb of OnSessionOpen method 44 ExtenderOnSessionOpenVerb = "extender.onSessionOpenVerb" 45 // ExtenderOnSessionCloseVerb is the verb of OnSessionClose method 46 ExtenderOnSessionCloseVerb = "extender.onSessionCloseVerb" 47 // ExtenderPredicateVerb is the verb of Predicate method 48 ExtenderPredicateVerb = "extender.predicateVerb" 49 // ExtenderPrioritizeVerb is the verb of Prioritize method 50 ExtenderPrioritizeVerb = "extender.prioritizeVerb" 51 // ExtenderPreemptableVerb is the verb of Preemptable method 52 ExtenderPreemptableVerb = "extender.preemptableVerb" 53 // ExtenderReclaimableVerb is the verb of Reclaimable method 54 ExtenderReclaimableVerb = "extender.reclaimableVerb" 55 // ExtenderQueueOverusedVerb is the verb of QueueOverused method 56 ExtenderQueueOverusedVerb = "extender.queueOverusedVerb" 57 // ExtenderJobEnqueueableVerb is the verb of JobEnqueueable method 58 ExtenderJobEnqueueableVerb = "extender.jobEnqueueableVerb" 59 // ExtenderJobReadyVerb is the verb of JobReady method 60 ExtenderJobReadyVerb = "extender.jobReadyVerb" 61 // ExtenderIgnorable indicates whether the extender can ignore unexpected errors 62 ExtenderIgnorable = "extender.ignorable" 63 ) 64 65 type extenderConfig struct { 66 urlPrefix string 67 httpTimeout time.Duration 68 onSessionOpenVerb string 69 onSessionCloseVerb string 70 predicateVerb string 71 prioritizeVerb string 72 preemptableVerb string 73 reclaimableVerb string 74 queueOverusedVerb string 75 jobEnqueueableVerb string 76 jobReadyVerb string 77 ignorable bool 78 } 79 80 type extenderPlugin struct { 81 client http.Client 82 config *extenderConfig 83 } 84 85 func parseExtenderConfig(arguments framework.Arguments) *extenderConfig { 86 /* 87 actions: "reclaim, allocate, backfill, preempt" 88 tiers: 89 - plugins: 90 - name: priority 91 - name: gang 92 - name: conformance 93 - plugins: 94 - name: drf 95 - name: predicates 96 - name: extender 97 arguments: 98 extender.urlPrefix: http://127.0.0.1 99 extender.httpTimeout: 100ms 100 extender.onSessionOpenVerb: onSessionOpen 101 extender.onSessionCloseVerb: onSessionClose 102 extender.predicateVerb: predicate 103 extender.prioritizeVerb: prioritize 104 extender.preemptableVerb: preemptable 105 extender.reclaimableVerb: reclaimable 106 extender.queueOverusedVerb: queueOverused 107 extender.jobEnqueueableVerb: jobEnqueueable 108 extender.ignorable: true 109 - name: proportion 110 - name: nodeorder 111 */ 112 ec := &extenderConfig{} 113 ec.urlPrefix, _ = arguments[ExtenderURLPrefix].(string) 114 ec.onSessionOpenVerb, _ = arguments[ExtenderOnSessionOpenVerb].(string) 115 ec.onSessionCloseVerb, _ = arguments[ExtenderOnSessionCloseVerb].(string) 116 ec.predicateVerb, _ = arguments[ExtenderPredicateVerb].(string) 117 ec.prioritizeVerb, _ = arguments[ExtenderPrioritizeVerb].(string) 118 ec.preemptableVerb, _ = arguments[ExtenderPreemptableVerb].(string) 119 ec.reclaimableVerb, _ = arguments[ExtenderReclaimableVerb].(string) 120 ec.queueOverusedVerb, _ = arguments[ExtenderQueueOverusedVerb].(string) 121 ec.jobEnqueueableVerb, _ = arguments[ExtenderJobEnqueueableVerb].(string) 122 ec.jobReadyVerb, _ = arguments[ExtenderJobReadyVerb].(string) 123 124 arguments.GetBool(&ec.ignorable, ExtenderIgnorable) 125 126 ec.httpTimeout = time.Second 127 if httpTimeout, _ := arguments[ExtenderHTTPTimeout].(string); httpTimeout != "" { 128 if timeoutDuration, err := time.ParseDuration(httpTimeout); err == nil { 129 ec.httpTimeout = timeoutDuration 130 } 131 } 132 133 return ec 134 } 135 136 func New(arguments framework.Arguments) framework.Plugin { 137 cfg := parseExtenderConfig(arguments) 138 klog.V(4).Infof("Initialize extender plugin with endpoint address %s", cfg.urlPrefix) 139 return &extenderPlugin{client: http.Client{Timeout: cfg.httpTimeout}, config: cfg} 140 } 141 142 func (ep *extenderPlugin) Name() string { 143 return PluginName 144 } 145 146 func (ep *extenderPlugin) OnSessionOpen(ssn *framework.Session) { 147 if ep.config.onSessionOpenVerb != "" { 148 err := ep.send(ep.config.onSessionOpenVerb, &OnSessionOpenRequest{ 149 Jobs: ssn.Jobs, 150 Nodes: ssn.Nodes, 151 Queues: ssn.Queues, 152 NamespaceInfo: ssn.NamespaceInfo, 153 RevocableNodes: ssn.RevocableNodes, 154 }, nil) 155 if err != nil { 156 klog.Warningf("OnSessionClose failed with error %v", err) 157 } 158 if err != nil && !ep.config.ignorable { 159 return 160 } 161 } 162 163 if ep.config.predicateVerb != "" { 164 ssn.AddPredicateFn(ep.Name(), func(task *api.TaskInfo, node *api.NodeInfo) ([]*api.Status, error) { 165 resp := &PredicateResponse{} 166 err := ep.send(ep.config.predicateVerb, &PredicateRequest{Task: task, Node: node}, resp) 167 if err != nil { 168 klog.Warningf("Predicate failed with error %v", err) 169 170 if ep.config.ignorable { 171 return nil, nil 172 } 173 return nil, err 174 } 175 176 predicateStatus := resp.Status 177 return predicateStatus, nil 178 }) 179 } 180 181 if ep.config.prioritizeVerb != "" { 182 ssn.AddBatchNodeOrderFn(ep.Name(), func(task *api.TaskInfo, nodes []*api.NodeInfo) (map[string]float64, error) { 183 resp := &PrioritizeResponse{} 184 err := ep.send(ep.config.prioritizeVerb, &PrioritizeRequest{Task: task, Nodes: nodes}, resp) 185 if err != nil { 186 klog.Warningf("Prioritize failed with error %v", err) 187 188 if ep.config.ignorable { 189 return nil, nil 190 } 191 return nil, err 192 } 193 194 if resp.ErrorMessage == "" && resp.NodeScore != nil { 195 return resp.NodeScore, nil 196 } 197 return nil, errors.New(resp.ErrorMessage) 198 }) 199 } 200 201 if ep.config.preemptableVerb != "" { 202 ssn.AddPreemptableFn(ep.Name(), func(evictor *api.TaskInfo, evictees []*api.TaskInfo) ([]*api.TaskInfo, int) { 203 resp := &PreemptableResponse{} 204 err := ep.send(ep.config.preemptableVerb, &PreemptableRequest{Evictor: evictor, Evictees: evictees}, resp) 205 if err != nil { 206 klog.Warningf("Preemptable failed with error %v", err) 207 208 if ep.config.ignorable { 209 return nil, util.Permit 210 } 211 return nil, util.Reject 212 } 213 214 return resp.Victims, resp.Status 215 }) 216 } 217 218 if ep.config.reclaimableVerb != "" { 219 ssn.AddReclaimableFn(ep.Name(), func(evictor *api.TaskInfo, evictees []*api.TaskInfo) ([]*api.TaskInfo, int) { 220 resp := &ReclaimableResponse{} 221 err := ep.send(ep.config.reclaimableVerb, &ReclaimableRequest{Evictor: evictor, Evictees: evictees}, resp) 222 if err != nil { 223 klog.Warningf("Reclaimable failed with error %v", err) 224 225 if ep.config.ignorable { 226 return nil, util.Permit 227 } 228 return nil, util.Reject 229 } 230 231 return resp.Victims, resp.Status 232 }) 233 } 234 235 if ep.config.jobEnqueueableVerb != "" { 236 ssn.AddJobEnqueueableFn(ep.Name(), func(obj interface{}) int { 237 job := obj.(*api.JobInfo) 238 resp := &JobEnqueueableResponse{} 239 err := ep.send(ep.config.jobEnqueueableVerb, &JobEnqueueableRequest{Job: job}, resp) 240 if err != nil { 241 klog.Warningf("JobEnqueueable failed with error %v", err) 242 243 if ep.config.ignorable { 244 return util.Permit 245 } 246 return util.Reject 247 } 248 249 return resp.Status 250 }) 251 } 252 253 if ep.config.queueOverusedVerb != "" { 254 ssn.AddOverusedFn(ep.Name(), func(obj interface{}) bool { 255 queue := obj.(*api.QueueInfo) 256 resp := &QueueOverusedResponse{} 257 err := ep.send(ep.config.queueOverusedVerb, &QueueOverusedRequest{Queue: queue}, resp) 258 if err != nil { 259 klog.Warningf("QueueOverused failed with error %v", err) 260 261 return !ep.config.ignorable 262 } 263 264 return resp.Overused 265 }) 266 } 267 268 if ep.config.jobReadyVerb != "" { 269 ssn.AddJobReadyFn(ep.Name(), func(obj interface{}) bool { 270 job := obj.(*api.JobInfo) 271 resp := &JobReadyResponse{} 272 err := ep.send(ep.config.jobReadyVerb, &JobReadyRequest{Job: job}, resp) 273 if err != nil { 274 klog.Warningf("JobReady failed with error %v", err) 275 276 return !ep.config.ignorable 277 } 278 279 return resp.Status 280 }) 281 } 282 } 283 284 func (ep *extenderPlugin) OnSessionClose(ssn *framework.Session) { 285 if ep.config.onSessionCloseVerb != "" { 286 if err := ep.send(ep.config.onSessionCloseVerb, &OnSessionCloseRequest{}, nil); err != nil { 287 klog.Warningf("OnSessionClose failed with error %v", err) 288 } 289 } 290 } 291 292 func (ep *extenderPlugin) send(action string, args interface{}, result interface{}) error { 293 out, err := json.Marshal(args) 294 if err != nil { 295 return err 296 } 297 298 url := strings.TrimRight(ep.config.urlPrefix, "/") + "/" + action 299 300 req, err := http.NewRequest("POST", url, bytes.NewReader(out)) 301 if err != nil { 302 return err 303 } 304 305 req.Header.Set("Content-Type", "application/json") 306 307 resp, err := ep.client.Do(req) 308 if err != nil { 309 return err 310 } 311 defer resp.Body.Close() 312 313 if resp.StatusCode != http.StatusOK { 314 return fmt.Errorf("failed %v with extender at URL %v, code %v", action, url, resp.StatusCode) 315 } 316 317 if result != nil { 318 return json.NewDecoder(resp.Body).Decode(result) 319 } 320 return nil 321 }