volcano.sh/volcano@v1.9.0/pkg/webhooks/admission/jobs/validate/util.go (about)

     1  /*
     2  Copyright 2018 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 validate
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/hashicorp/go-multierror"
    23  	"k8s.io/apimachinery/pkg/util/validation/field"
    24  	"k8s.io/kubernetes/pkg/apis/core/validation"
    25  
    26  	batchv1alpha1 "volcano.sh/apis/pkg/apis/batch/v1alpha1"
    27  	busv1alpha1 "volcano.sh/apis/pkg/apis/bus/v1alpha1"
    28  )
    29  
    30  // policyEventMap defines all policy events and whether to allow external use.
    31  var policyEventMap = map[busv1alpha1.Event]bool{
    32  	busv1alpha1.AnyEvent:           true,
    33  	busv1alpha1.PodFailedEvent:     true,
    34  	busv1alpha1.PodEvictedEvent:    true,
    35  	busv1alpha1.JobUnknownEvent:    true,
    36  	busv1alpha1.TaskCompletedEvent: true,
    37  	busv1alpha1.TaskFailedEvent:    true,
    38  	busv1alpha1.OutOfSyncEvent:     false,
    39  	busv1alpha1.CommandIssuedEvent: false,
    40  	busv1alpha1.JobUpdatedEvent:    true,
    41  }
    42  
    43  // policyActionMap defines all policy actions and whether to allow external use.
    44  var policyActionMap = map[busv1alpha1.Action]bool{
    45  	busv1alpha1.AbortJobAction:     true,
    46  	busv1alpha1.RestartJobAction:   true,
    47  	busv1alpha1.RestartTaskAction:  true,
    48  	busv1alpha1.TerminateJobAction: true,
    49  	busv1alpha1.CompleteJobAction:  true,
    50  	busv1alpha1.ResumeJobAction:    true,
    51  	busv1alpha1.SyncJobAction:      false,
    52  	busv1alpha1.EnqueueAction:      false,
    53  	busv1alpha1.SyncQueueAction:    false,
    54  	busv1alpha1.OpenQueueAction:    false,
    55  	busv1alpha1.CloseQueueAction:   false,
    56  }
    57  
    58  func validatePolicies(policies []batchv1alpha1.LifecyclePolicy, fldPath *field.Path) error {
    59  	var err error
    60  	policyEvents := map[busv1alpha1.Event]struct{}{}
    61  	exitCodes := map[int32]struct{}{}
    62  
    63  	for _, policy := range policies {
    64  		if (policy.Event != "" || len(policy.Events) != 0) && policy.ExitCode != nil {
    65  			err = multierror.Append(err, fmt.Errorf("must not specify event and exitCode simultaneously"))
    66  			break
    67  		}
    68  
    69  		if policy.Event == "" && len(policy.Events) == 0 && policy.ExitCode == nil {
    70  			err = multierror.Append(err, fmt.Errorf("either event and exitCode should be specified"))
    71  			break
    72  		}
    73  
    74  		if len(policy.Event) != 0 || len(policy.Events) != 0 {
    75  			bFlag := false
    76  			policyEventsList := getEventList(policy)
    77  			for _, event := range policyEventsList {
    78  				if allow, ok := policyEventMap[event]; !ok || !allow {
    79  					err = multierror.Append(err, field.Invalid(fldPath, event, "invalid policy event"))
    80  					bFlag = true
    81  					break
    82  				}
    83  
    84  				if allow, ok := policyActionMap[policy.Action]; !ok || !allow {
    85  					err = multierror.Append(err, field.Invalid(fldPath, policy.Action, "invalid policy action"))
    86  					bFlag = true
    87  					break
    88  				}
    89  				if _, found := policyEvents[event]; found {
    90  					err = multierror.Append(err, fmt.Errorf("duplicate event %v  across different policy", event))
    91  					bFlag = true
    92  					break
    93  				} else {
    94  					policyEvents[event] = struct{}{}
    95  				}
    96  			}
    97  			if bFlag {
    98  				break
    99  			}
   100  		} else {
   101  			if *policy.ExitCode == 0 {
   102  				err = multierror.Append(err, fmt.Errorf("0 is not a valid error code"))
   103  				break
   104  			}
   105  			if _, found := exitCodes[*policy.ExitCode]; found {
   106  				err = multierror.Append(err, fmt.Errorf("duplicate exitCode %v", *policy.ExitCode))
   107  				break
   108  			} else {
   109  				exitCodes[*policy.ExitCode] = struct{}{}
   110  			}
   111  		}
   112  	}
   113  
   114  	if _, found := policyEvents[busv1alpha1.AnyEvent]; found && len(policyEvents) > 1 {
   115  		err = multierror.Append(err, fmt.Errorf("if there's * here, no other policy should be here"))
   116  	}
   117  
   118  	return err
   119  }
   120  
   121  func getEventList(policy batchv1alpha1.LifecyclePolicy) []busv1alpha1.Event {
   122  	policyEventsList := policy.Events
   123  	if len(policy.Event) > 0 {
   124  		policyEventsList = append(policyEventsList, policy.Event)
   125  	}
   126  	uniquePolicyEventlist := removeDuplicates(policyEventsList)
   127  	return uniquePolicyEventlist
   128  }
   129  
   130  func removeDuplicates(eventList []busv1alpha1.Event) []busv1alpha1.Event {
   131  	keys := make(map[busv1alpha1.Event]bool)
   132  	list := []busv1alpha1.Event{}
   133  	for _, val := range eventList {
   134  		if _, value := keys[val]; !value {
   135  			keys[val] = true
   136  			list = append(list, val)
   137  		}
   138  	}
   139  	return list
   140  }
   141  
   142  func getValidEvents() []busv1alpha1.Event {
   143  	var events []busv1alpha1.Event
   144  	for e, allow := range policyEventMap {
   145  		if allow {
   146  			events = append(events, e)
   147  		}
   148  	}
   149  
   150  	return events
   151  }
   152  
   153  func getValidActions() []busv1alpha1.Action {
   154  	var actions []busv1alpha1.Action
   155  	for a, allow := range policyActionMap {
   156  		if allow {
   157  			actions = append(actions, a)
   158  		}
   159  	}
   160  
   161  	return actions
   162  }
   163  
   164  // validateIO validates IO configuration.
   165  func validateIO(volumes []batchv1alpha1.VolumeSpec) error {
   166  	volumeMap := map[string]bool{}
   167  	for _, volume := range volumes {
   168  		if len(volume.MountPath) == 0 {
   169  			return fmt.Errorf(" mountPath is required;")
   170  		}
   171  		if _, found := volumeMap[volume.MountPath]; found {
   172  			return fmt.Errorf(" duplicated mountPath: %s;", volume.MountPath)
   173  		}
   174  		if volume.VolumeClaim == nil && volume.VolumeClaimName == "" {
   175  			return fmt.Errorf(" either VolumeClaim or VolumeClaimName must be specified;")
   176  		}
   177  		if len(volume.VolumeClaimName) != 0 {
   178  			if volume.VolumeClaim != nil {
   179  				return fmt.Errorf("conflict: If you want to use an existing PVC, just specify VolumeClaimName." +
   180  					"If you want to create a new PVC, you do not need to specify VolumeClaimName")
   181  			}
   182  			if errMsgs := validation.ValidatePersistentVolumeName(volume.VolumeClaimName, false); len(errMsgs) > 0 {
   183  				return fmt.Errorf("invalid VolumeClaimName %s : %v", volume.VolumeClaimName, errMsgs)
   184  			}
   185  		}
   186  
   187  		volumeMap[volume.MountPath] = true
   188  	}
   189  	return nil
   190  }
   191  
   192  // topoSort uses topo sort to sort job tasks based on dependsOn field
   193  // it will return an array contains all sorted task names and a bool which indicates whether it's a valid dag
   194  func topoSort(job *batchv1alpha1.Job) ([]string, bool) {
   195  	graph, inDegree, taskList := makeGraph(job)
   196  	var taskStack []string
   197  	for task, degree := range inDegree {
   198  		if degree == 0 {
   199  			taskStack = append(taskStack, task)
   200  		}
   201  	}
   202  
   203  	sortedTasks := make([]string, 0)
   204  	for len(taskStack) > 0 {
   205  		length := len(taskStack)
   206  		var out string
   207  		out, taskStack = taskStack[length-1], taskStack[:length-1]
   208  		sortedTasks = append(sortedTasks, out)
   209  		for in, connected := range graph[out] {
   210  			if connected {
   211  				graph[out][in] = false
   212  				inDegree[in]--
   213  				if inDegree[in] == 0 {
   214  					taskStack = append(taskStack, in)
   215  				}
   216  			}
   217  		}
   218  	}
   219  
   220  	isDag := len(sortedTasks) == len(taskList)
   221  	if !isDag {
   222  		return nil, false
   223  	}
   224  
   225  	return sortedTasks, isDag
   226  }
   227  
   228  func makeGraph(job *batchv1alpha1.Job) (map[string]map[string]bool, map[string]int, []string) {
   229  	graph := make(map[string]map[string]bool)
   230  	inDegree := make(map[string]int)
   231  	taskList := make([]string, 0)
   232  
   233  	for _, task := range job.Spec.Tasks {
   234  		taskList = append(taskList, task.Name)
   235  		inDegree[task.Name] = 0
   236  		if task.DependsOn != nil {
   237  			for _, dependOnTask := range task.DependsOn.Name {
   238  				if graph[dependOnTask] == nil {
   239  					graph[dependOnTask] = make(map[string]bool)
   240  				}
   241  
   242  				graph[dependOnTask][task.Name] = true
   243  				inDegree[task.Name]++
   244  			}
   245  		}
   246  	}
   247  
   248  	return graph, inDegree, taskList
   249  }