volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/sla/sla.go (about)

     1  /*
     2  Copyright 2021 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 sla
    18  
    19  import (
    20  	"time"
    21  
    22  	"k8s.io/klog/v2"
    23  
    24  	"volcano.sh/volcano/pkg/scheduler/api"
    25  	"volcano.sh/volcano/pkg/scheduler/framework"
    26  	"volcano.sh/volcano/pkg/scheduler/plugins/util"
    27  )
    28  
    29  const (
    30  	// PluginName indicates name of volcano scheduler plugin
    31  	PluginName = "sla"
    32  	// JobWaitingTime is maximum waiting time that a job could stay Pending in service level agreement
    33  	// when job waits longer than waiting time, it should be inqueue at once, and cluster should reserve resources for it
    34  	// Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”
    35  	JobWaitingTime = "sla-waiting-time"
    36  )
    37  
    38  type slaPlugin struct {
    39  	// Arguments given for sla plugin
    40  	pluginArguments framework.Arguments
    41  	jobWaitingTime  *time.Duration
    42  }
    43  
    44  // New function returns sla plugin object
    45  func New(arguments framework.Arguments) framework.Plugin {
    46  	return &slaPlugin{
    47  		pluginArguments: arguments,
    48  		jobWaitingTime:  nil,
    49  	}
    50  }
    51  
    52  func (sp *slaPlugin) Name() string {
    53  	return PluginName
    54  }
    55  
    56  // readJobWaitingTime read job waiting time from jobInfo or sla plugin arguments
    57  // Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”
    58  func (sp *slaPlugin) readJobWaitingTime(jwt *time.Duration) *time.Duration {
    59  	// read individual jobInfo waiting time from jobInfos
    60  	if jwt == nil {
    61  		// if no individual settings, read global jobInfo waiting time from sla plugin arguments
    62  		return sp.jobWaitingTime
    63  	}
    64  	return jwt
    65  }
    66  
    67  /*
    68  User should give global job waiting time settings via sla plugin arguments:
    69  actions: "enqueue, allocate, backfill"
    70  tiers:
    71  - plugins:
    72    - name: sla
    73      arguments:
    74      sla-waiting-time: 1h2m3s4ms5µs6ns
    75  
    76  Meanwhile, use can give individual job waiting time settings for one job via job annotations:
    77  apiVersion: batch.volcano.sh/v1alpha1
    78  kind: Job
    79  metadata:
    80  
    81  	annotations:
    82  	  sla-waiting-time: 1h2m3s4ms5us6ns
    83  */
    84  func (sp *slaPlugin) OnSessionOpen(ssn *framework.Session) {
    85  	klog.V(4).Infof("Enter sla plugin ...")
    86  	defer klog.V(4).Infof("Leaving sla plugin.")
    87  
    88  	// read in sla waiting time for global cluster from sla plugin arguments
    89  	// if not set, job waiting time still can set in job yaml separately, otherwise job have no sla limits
    90  	if _, exist := sp.pluginArguments[JobWaitingTime]; exist {
    91  		waitTime, ok := sp.pluginArguments[JobWaitingTime].(string)
    92  		if !ok {
    93  			waitTime = ""
    94  		}
    95  		jwt, err := time.ParseDuration(waitTime)
    96  		if err != nil {
    97  			klog.Errorf("Error occurs in parsing global job waiting time in sla plugin, err: %s.", err.Error())
    98  		}
    99  
   100  		if jwt <= 0 {
   101  			klog.Warningf("Invalid global waiting time setting: %s in sla plugin.", jwt.String())
   102  		} else {
   103  			sp.jobWaitingTime = &jwt
   104  			klog.V(4).Infof("Global job waiting time is %s.", sp.jobWaitingTime.String())
   105  		}
   106  	}
   107  
   108  	jobOrderFn := func(l, r interface{}) int {
   109  		lv := l.(*api.JobInfo)
   110  		rv := r.(*api.JobInfo)
   111  
   112  		var lJobWaitingTime = sp.readJobWaitingTime(lv.WaitingTime)
   113  		var rJobWaitingTime = sp.readJobWaitingTime(rv.WaitingTime)
   114  
   115  		if lJobWaitingTime == nil {
   116  			if rJobWaitingTime == nil {
   117  				return 0
   118  			}
   119  			return 1
   120  		}
   121  		if rJobWaitingTime == nil {
   122  			return -1
   123  		}
   124  
   125  		lCreationTimestamp := lv.CreationTimestamp
   126  		rCreationTimestamp := rv.CreationTimestamp
   127  		if lCreationTimestamp.Add(*lJobWaitingTime).Before(rCreationTimestamp.Add(*rJobWaitingTime)) {
   128  			return -1
   129  		} else if lCreationTimestamp.Add(*lJobWaitingTime).After(rCreationTimestamp.Add(*rJobWaitingTime)) {
   130  			return 1
   131  		}
   132  		return 0
   133  	}
   134  	ssn.AddJobOrderFn(sp.Name(), jobOrderFn)
   135  
   136  	permitableFn := func(obj interface{}) int {
   137  		jobInfo := obj.(*api.JobInfo)
   138  		var jwt = sp.readJobWaitingTime(jobInfo.WaitingTime)
   139  
   140  		if jwt == nil {
   141  			return util.Abstain
   142  		}
   143  
   144  		if time.Since(jobInfo.CreationTimestamp.Time) < *jwt {
   145  			return util.Abstain
   146  		}
   147  
   148  		return util.Permit
   149  	}
   150  	// if job waiting time is over, turn job to be inqueue in enqueue action
   151  	ssn.AddJobEnqueueableFn(sp.Name(), permitableFn)
   152  	// if job waiting time is over, turn job to be pipelined in allocate action
   153  	ssn.AddJobPipelinedFn(sp.Name(), permitableFn)
   154  }
   155  
   156  func (sp *slaPlugin) OnSessionClose(ssn *framework.Session) {}