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

     1  /*
     2  Copyright 2019 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  	"context"
    21  	"strings"
    22  	"testing"
    23  
    24  	admissionv1 "k8s.io/api/admission/v1"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/resource"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  
    30  	"volcano.sh/apis/pkg/apis/batch/v1alpha1"
    31  	busv1alpha1 "volcano.sh/apis/pkg/apis/bus/v1alpha1"
    32  	schedulingv1beta2 "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
    33  	fakeclient "volcano.sh/apis/pkg/client/clientset/versioned/fake"
    34  )
    35  
    36  func TestValidateJobCreate(t *testing.T) {
    37  	var invTTL int32 = -1
    38  	var policyExitCode int32 = -1
    39  	var invMinAvailable int32 = -1
    40  	namespace := "test"
    41  	priviledged := true
    42  
    43  	testCases := []struct {
    44  		Name           string
    45  		Job            v1alpha1.Job
    46  		ExpectErr      bool
    47  		reviewResponse admissionv1.AdmissionResponse
    48  		ret            string
    49  	}{
    50  		{
    51  			Name: "validate valid-job",
    52  			Job: v1alpha1.Job{
    53  				ObjectMeta: metav1.ObjectMeta{
    54  					Name:      "valid-job",
    55  					Namespace: namespace,
    56  				},
    57  				Spec: v1alpha1.JobSpec{
    58  					MinAvailable: 1,
    59  					Queue:        "default",
    60  					Tasks: []v1alpha1.TaskSpec{
    61  						{
    62  							Name:     "task-1",
    63  							Replicas: 1,
    64  							Template: v1.PodTemplateSpec{
    65  								ObjectMeta: metav1.ObjectMeta{
    66  									Labels: map[string]string{"name": "test"},
    67  								},
    68  								Spec: v1.PodSpec{
    69  									Containers: []v1.Container{
    70  										{
    71  											Name:  "fake-name",
    72  											Image: "busybox:1.24",
    73  										},
    74  									},
    75  								},
    76  							},
    77  						},
    78  					},
    79  					Policies: []v1alpha1.LifecyclePolicy{
    80  						{
    81  							Event:  busv1alpha1.PodEvictedEvent,
    82  							Action: busv1alpha1.RestartTaskAction,
    83  						},
    84  					},
    85  				},
    86  			},
    87  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
    88  			ret:            "",
    89  			ExpectErr:      false,
    90  		},
    91  		// duplicate task name
    92  		{
    93  			Name: "duplicate-task-job",
    94  			Job: v1alpha1.Job{
    95  				ObjectMeta: metav1.ObjectMeta{
    96  					Name:      "duplicate-task-job",
    97  					Namespace: namespace,
    98  				},
    99  				Spec: v1alpha1.JobSpec{
   100  					MinAvailable: 1,
   101  					Queue:        "default",
   102  					Tasks: []v1alpha1.TaskSpec{
   103  						{
   104  							Name:     "duplicated-task-1",
   105  							Replicas: 1,
   106  							Template: v1.PodTemplateSpec{
   107  								ObjectMeta: metav1.ObjectMeta{
   108  									Labels: map[string]string{"name": "test"},
   109  								},
   110  								Spec: v1.PodSpec{
   111  									Containers: []v1.Container{
   112  										{
   113  											Name:  "fake-name",
   114  											Image: "busybox:1.24",
   115  										},
   116  									},
   117  								},
   118  							},
   119  						},
   120  						{
   121  							Name:     "duplicated-task-1",
   122  							Replicas: 1,
   123  							Template: v1.PodTemplateSpec{
   124  								ObjectMeta: metav1.ObjectMeta{
   125  									Labels: map[string]string{"name": "test"},
   126  								},
   127  								Spec: v1.PodSpec{
   128  									Containers: []v1.Container{
   129  										{
   130  											Name:  "fake-name",
   131  											Image: "busybox:1.24",
   132  										},
   133  									},
   134  								},
   135  							},
   136  						},
   137  					},
   138  				},
   139  			},
   140  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   141  			ret:            "duplicated task name duplicated-task-1",
   142  			ExpectErr:      true,
   143  		},
   144  		// Duplicated Policy Event
   145  		{
   146  			Name: "job-policy-duplicated",
   147  			Job: v1alpha1.Job{
   148  				ObjectMeta: metav1.ObjectMeta{
   149  					Name:      "job-policy-duplicated",
   150  					Namespace: namespace,
   151  				},
   152  				Spec: v1alpha1.JobSpec{
   153  					MinAvailable: 1,
   154  					Queue:        "default",
   155  					Tasks: []v1alpha1.TaskSpec{
   156  						{
   157  							Name:     "task-1",
   158  							Replicas: 1,
   159  							Template: v1.PodTemplateSpec{
   160  								ObjectMeta: metav1.ObjectMeta{
   161  									Labels: map[string]string{"name": "test"},
   162  								},
   163  								Spec: v1.PodSpec{
   164  									Containers: []v1.Container{
   165  										{
   166  											Name:  "fake-name",
   167  											Image: "busybox:1.24",
   168  										},
   169  									},
   170  								},
   171  							},
   172  						},
   173  					},
   174  					Policies: []v1alpha1.LifecyclePolicy{
   175  						{
   176  							Event:  busv1alpha1.PodFailedEvent,
   177  							Action: busv1alpha1.AbortJobAction,
   178  						},
   179  						{
   180  							Event:  busv1alpha1.PodFailedEvent,
   181  							Action: busv1alpha1.RestartJobAction,
   182  						},
   183  					},
   184  				},
   185  			},
   186  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   187  			ret:            "duplicate",
   188  			ExpectErr:      true,
   189  		},
   190  		// Min Available illegal
   191  		{
   192  			Name: "Min Available illegal",
   193  			Job: v1alpha1.Job{
   194  				ObjectMeta: metav1.ObjectMeta{
   195  					Name:      "job-min-illegal",
   196  					Namespace: namespace,
   197  				},
   198  				Spec: v1alpha1.JobSpec{
   199  					MinAvailable: 2,
   200  					Queue:        "default",
   201  					Tasks: []v1alpha1.TaskSpec{
   202  						{
   203  							Name:     "task-1",
   204  							Replicas: 1,
   205  							Template: v1.PodTemplateSpec{
   206  								ObjectMeta: metav1.ObjectMeta{
   207  									Labels: map[string]string{"name": "test"},
   208  								},
   209  								Spec: v1.PodSpec{
   210  									Containers: []v1.Container{
   211  										{
   212  											Name:  "fake-name",
   213  											Image: "busybox:1.24",
   214  										},
   215  									},
   216  								},
   217  							},
   218  						},
   219  					},
   220  				},
   221  			},
   222  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   223  			ret:            "job 'minAvailable' should not be greater than total replicas in tasks",
   224  			ExpectErr:      true,
   225  		},
   226  		// Job Plugin illegal
   227  		{
   228  			Name: "Job Plugin illegal",
   229  			Job: v1alpha1.Job{
   230  				ObjectMeta: metav1.ObjectMeta{
   231  					Name:      "job-plugin-illegal",
   232  					Namespace: namespace,
   233  				},
   234  				Spec: v1alpha1.JobSpec{
   235  					MinAvailable: 1,
   236  					Queue:        "default",
   237  					Tasks: []v1alpha1.TaskSpec{
   238  						{
   239  							Name:     "task-1",
   240  							Replicas: 1,
   241  							Template: v1.PodTemplateSpec{
   242  								ObjectMeta: metav1.ObjectMeta{
   243  									Labels: map[string]string{"name": "test"},
   244  								},
   245  								Spec: v1.PodSpec{
   246  									Containers: []v1.Container{
   247  										{
   248  											Name:  "fake-name",
   249  											Image: "busybox:1.24",
   250  										},
   251  									},
   252  								},
   253  							},
   254  						},
   255  					},
   256  					Plugins: map[string][]string{
   257  						"big_plugin": {},
   258  					},
   259  				},
   260  			},
   261  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   262  			ret:            "unable to find job plugin: big_plugin",
   263  			ExpectErr:      true,
   264  		},
   265  		// ttl-illegal
   266  		{
   267  			Name: "job-ttl-illegal",
   268  			Job: v1alpha1.Job{
   269  				ObjectMeta: metav1.ObjectMeta{
   270  					Name:      "job-ttl-illegal",
   271  					Namespace: namespace,
   272  				},
   273  				Spec: v1alpha1.JobSpec{
   274  					MinAvailable: 1,
   275  					Queue:        "default",
   276  					Tasks: []v1alpha1.TaskSpec{
   277  						{
   278  							Name:     "task-1",
   279  							Replicas: 1,
   280  							Template: v1.PodTemplateSpec{
   281  								ObjectMeta: metav1.ObjectMeta{
   282  									Labels: map[string]string{"name": "test"},
   283  								},
   284  								Spec: v1.PodSpec{
   285  									Containers: []v1.Container{
   286  										{
   287  											Name:  "fake-name",
   288  											Image: "busybox:1.24",
   289  										},
   290  									},
   291  								},
   292  							},
   293  						},
   294  					},
   295  					TTLSecondsAfterFinished: &invTTL,
   296  				},
   297  			},
   298  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   299  			ret:            "'ttlSecondsAfterFinished' cannot be less than zero",
   300  			ExpectErr:      true,
   301  		},
   302  		// min-MinAvailable less than zero
   303  		{
   304  			Name: "minAvailable-lessThanZero",
   305  			Job: v1alpha1.Job{
   306  				ObjectMeta: metav1.ObjectMeta{
   307  					Name:      "minAvailable-lessThanZero",
   308  					Namespace: namespace,
   309  				},
   310  				Spec: v1alpha1.JobSpec{
   311  					MinAvailable: -1,
   312  					Queue:        "default",
   313  					Tasks: []v1alpha1.TaskSpec{
   314  						{
   315  							Name:     "task-1",
   316  							Replicas: 1,
   317  							Template: v1.PodTemplateSpec{
   318  								ObjectMeta: metav1.ObjectMeta{
   319  									Labels: map[string]string{"name": "test"},
   320  								},
   321  								Spec: v1.PodSpec{
   322  									Containers: []v1.Container{
   323  										{
   324  											Name:  "fake-name",
   325  											Image: "busybox:1.24",
   326  										},
   327  									},
   328  								},
   329  							},
   330  						},
   331  					},
   332  				},
   333  			},
   334  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   335  			ret:            "job 'minAvailable' must be >= 0",
   336  			ExpectErr:      true,
   337  		},
   338  		// maxretry less than zero
   339  		{
   340  			Name: "maxretry-lessThanZero",
   341  			Job: v1alpha1.Job{
   342  				ObjectMeta: metav1.ObjectMeta{
   343  					Name:      "maxretry-lessThanZero",
   344  					Namespace: namespace,
   345  				},
   346  				Spec: v1alpha1.JobSpec{
   347  					MinAvailable: 1,
   348  					MaxRetry:     -1,
   349  					Queue:        "default",
   350  					Tasks: []v1alpha1.TaskSpec{
   351  						{
   352  							Name:     "task-1",
   353  							Replicas: 1,
   354  							Template: v1.PodTemplateSpec{
   355  								ObjectMeta: metav1.ObjectMeta{
   356  									Labels: map[string]string{"name": "test"},
   357  								},
   358  								Spec: v1.PodSpec{
   359  									Containers: []v1.Container{
   360  										{
   361  											Name:  "fake-name",
   362  											Image: "busybox:1.24",
   363  										},
   364  									},
   365  								},
   366  							},
   367  						},
   368  					},
   369  				},
   370  			},
   371  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   372  			ret:            "'maxRetry' cannot be less than zero.",
   373  			ExpectErr:      true,
   374  		},
   375  		// no task specified in the job
   376  		{
   377  			Name: "no-task",
   378  			Job: v1alpha1.Job{
   379  				ObjectMeta: metav1.ObjectMeta{
   380  					Name:      "no-task",
   381  					Namespace: namespace,
   382  				},
   383  				Spec: v1alpha1.JobSpec{
   384  					MinAvailable: 1,
   385  					Queue:        "default",
   386  					Tasks:        []v1alpha1.TaskSpec{},
   387  				},
   388  			},
   389  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   390  			ret:            "No task specified in job spec",
   391  			ExpectErr:      true,
   392  		},
   393  		// replica set less than zero
   394  		{
   395  			Name: "replica-lessThanZero",
   396  			Job: v1alpha1.Job{
   397  				ObjectMeta: metav1.ObjectMeta{
   398  					Name:      "replica-lessThanZero",
   399  					Namespace: namespace,
   400  				},
   401  				Spec: v1alpha1.JobSpec{
   402  					MinAvailable: 1,
   403  					Queue:        "default",
   404  					Tasks: []v1alpha1.TaskSpec{
   405  						{
   406  							Name:     "task-1",
   407  							Replicas: -1,
   408  							Template: v1.PodTemplateSpec{
   409  								ObjectMeta: metav1.ObjectMeta{
   410  									Labels: map[string]string{"name": "test"},
   411  								},
   412  								Spec: v1.PodSpec{
   413  									Containers: []v1.Container{
   414  										{
   415  											Name:  "fake-name",
   416  											Image: "busybox:1.24",
   417  										},
   418  									},
   419  								},
   420  							},
   421  						},
   422  					},
   423  				},
   424  			},
   425  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   426  			ret:            "'replicas' < 0 in task: task-1, job: replica-lessThanZero; job 'minAvailable' should not be greater than total replicas in tasks;",
   427  			ExpectErr:      true,
   428  		},
   429  		// task minAvailable set less than zero
   430  		{
   431  			Name: "replica-lessThanZero",
   432  			Job: v1alpha1.Job{
   433  				ObjectMeta: metav1.ObjectMeta{
   434  					Name:      "taskMinAvailable-lessThanZero",
   435  					Namespace: namespace,
   436  				},
   437  				Spec: v1alpha1.JobSpec{
   438  					MinAvailable: 1,
   439  					Queue:        "default",
   440  					Tasks: []v1alpha1.TaskSpec{
   441  						{
   442  							Name:         "task-1",
   443  							Replicas:     1,
   444  							MinAvailable: &invMinAvailable,
   445  							Template: v1.PodTemplateSpec{
   446  								ObjectMeta: metav1.ObjectMeta{
   447  									Labels: map[string]string{"name": "test"},
   448  								},
   449  								Spec: v1.PodSpec{
   450  									Containers: []v1.Container{
   451  										{
   452  											Name:  "fake-name",
   453  											Image: "busybox:1.24",
   454  										},
   455  									},
   456  								},
   457  							},
   458  						},
   459  					},
   460  				},
   461  			},
   462  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   463  			ret:            "'minAvailable' < 0 in task: task-1, job: taskMinAvailable-lessThanZero;",
   464  			ExpectErr:      true,
   465  		},
   466  		// task name error
   467  		{
   468  			Name: "nonDNS-task",
   469  			Job: v1alpha1.Job{
   470  				ObjectMeta: metav1.ObjectMeta{
   471  					Name:      "replica-lessThanZero",
   472  					Namespace: namespace,
   473  				},
   474  				Spec: v1alpha1.JobSpec{
   475  					MinAvailable: 1,
   476  					Queue:        "default",
   477  					Tasks: []v1alpha1.TaskSpec{
   478  						{
   479  							Name:     "Task-1",
   480  							Replicas: 1,
   481  							Template: v1.PodTemplateSpec{
   482  								ObjectMeta: metav1.ObjectMeta{
   483  									Labels: map[string]string{"name": "test"},
   484  								},
   485  								Spec: v1.PodSpec{
   486  									Containers: []v1.Container{
   487  										{
   488  											Name:  "fake-name",
   489  											Image: "busybox:1.24",
   490  										},
   491  									},
   492  								},
   493  							},
   494  						},
   495  					},
   496  				},
   497  			},
   498  			reviewResponse: admissionv1.AdmissionResponse{Allowed: false},
   499  			ret:            "[a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')];",
   500  			ExpectErr:      true,
   501  		},
   502  		// Policy Event with exit code
   503  		{
   504  			Name: "job-policy-withExitCode",
   505  			Job: v1alpha1.Job{
   506  				ObjectMeta: metav1.ObjectMeta{
   507  					Name:      "job-policy-withExitCode",
   508  					Namespace: namespace,
   509  				},
   510  				Spec: v1alpha1.JobSpec{
   511  					MinAvailable: 1,
   512  					Queue:        "default",
   513  					Tasks: []v1alpha1.TaskSpec{
   514  						{
   515  							Name:     "task-1",
   516  							Replicas: 1,
   517  							Template: v1.PodTemplateSpec{
   518  								ObjectMeta: metav1.ObjectMeta{
   519  									Labels: map[string]string{"name": "test"},
   520  								},
   521  								Spec: v1.PodSpec{
   522  									Containers: []v1.Container{
   523  										{
   524  											Name:  "fake-name",
   525  											Image: "busybox:1.24",
   526  										},
   527  									},
   528  								},
   529  							},
   530  						},
   531  					},
   532  					Policies: []v1alpha1.LifecyclePolicy{
   533  						{
   534  							Event:    busv1alpha1.PodFailedEvent,
   535  							Action:   busv1alpha1.AbortJobAction,
   536  							ExitCode: &policyExitCode,
   537  						},
   538  					},
   539  				},
   540  			},
   541  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   542  			ret:            "must not specify event and exitCode simultaneously",
   543  			ExpectErr:      true,
   544  		},
   545  		// Both policy event and exit code are nil
   546  		{
   547  			Name: "policy-noEvent-noExCode",
   548  			Job: v1alpha1.Job{
   549  				ObjectMeta: metav1.ObjectMeta{
   550  					Name:      "policy-noEvent-noExCode",
   551  					Namespace: namespace,
   552  				},
   553  				Spec: v1alpha1.JobSpec{
   554  					MinAvailable: 1,
   555  					Queue:        "default",
   556  					Tasks: []v1alpha1.TaskSpec{
   557  						{
   558  							Name:     "task-1",
   559  							Replicas: 1,
   560  							Template: v1.PodTemplateSpec{
   561  								ObjectMeta: metav1.ObjectMeta{
   562  									Labels: map[string]string{"name": "test"},
   563  								},
   564  								Spec: v1.PodSpec{
   565  									Containers: []v1.Container{
   566  										{
   567  											Name:  "fake-name",
   568  											Image: "busybox:1.24",
   569  										},
   570  									},
   571  								},
   572  							},
   573  						},
   574  					},
   575  					Policies: []v1alpha1.LifecyclePolicy{
   576  						{
   577  							Action: busv1alpha1.AbortJobAction,
   578  						},
   579  					},
   580  				},
   581  			},
   582  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   583  			ret:            "either event and exitCode should be specified",
   584  			ExpectErr:      true,
   585  		},
   586  		// invalid policy event
   587  		{
   588  			Name: "invalid-policy-event",
   589  			Job: v1alpha1.Job{
   590  				ObjectMeta: metav1.ObjectMeta{
   591  					Name:      "invalid-policy-event",
   592  					Namespace: namespace,
   593  				},
   594  				Spec: v1alpha1.JobSpec{
   595  					MinAvailable: 1,
   596  					Queue:        "default",
   597  					Tasks: []v1alpha1.TaskSpec{
   598  						{
   599  							Name:     "task-1",
   600  							Replicas: 1,
   601  							Template: v1.PodTemplateSpec{
   602  								ObjectMeta: metav1.ObjectMeta{
   603  									Labels: map[string]string{"name": "test"},
   604  								},
   605  								Spec: v1.PodSpec{
   606  									Containers: []v1.Container{
   607  										{
   608  											Name:  "fake-name",
   609  											Image: "busybox:1.24",
   610  										},
   611  									},
   612  								},
   613  							},
   614  						},
   615  					},
   616  					Policies: []v1alpha1.LifecyclePolicy{
   617  						{
   618  							Event:  busv1alpha1.Event("someFakeEvent"),
   619  							Action: busv1alpha1.AbortJobAction,
   620  						},
   621  					},
   622  				},
   623  			},
   624  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   625  			ret:            "invalid policy event",
   626  			ExpectErr:      true,
   627  		},
   628  		// invalid policy action
   629  		{
   630  			Name: "invalid-policy-action",
   631  			Job: v1alpha1.Job{
   632  				ObjectMeta: metav1.ObjectMeta{
   633  					Name:      "invalid-policy-action",
   634  					Namespace: namespace,
   635  				},
   636  				Spec: v1alpha1.JobSpec{
   637  					MinAvailable: 1,
   638  					Queue:        "default",
   639  					Tasks: []v1alpha1.TaskSpec{
   640  						{
   641  							Name:     "task-1",
   642  							Replicas: 1,
   643  							Template: v1.PodTemplateSpec{
   644  								ObjectMeta: metav1.ObjectMeta{
   645  									Labels: map[string]string{"name": "test"},
   646  								},
   647  								Spec: v1.PodSpec{
   648  									Containers: []v1.Container{
   649  										{
   650  											Name:  "fake-name",
   651  											Image: "busybox:1.24",
   652  										},
   653  									},
   654  								},
   655  							},
   656  						},
   657  					},
   658  					Policies: []v1alpha1.LifecyclePolicy{
   659  						{
   660  							Event:  busv1alpha1.PodEvictedEvent,
   661  							Action: busv1alpha1.Action("someFakeAction"),
   662  						},
   663  					},
   664  				},
   665  			},
   666  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   667  			ret:            "invalid policy action",
   668  			ExpectErr:      true,
   669  		},
   670  		// policy exit-code zero
   671  		{
   672  			Name: "policy-extcode-zero",
   673  			Job: v1alpha1.Job{
   674  				ObjectMeta: metav1.ObjectMeta{
   675  					Name:      "policy-extcode-zero",
   676  					Namespace: namespace,
   677  				},
   678  				Spec: v1alpha1.JobSpec{
   679  					MinAvailable: 1,
   680  					Queue:        "default",
   681  					Tasks: []v1alpha1.TaskSpec{
   682  						{
   683  							Name:     "task-1",
   684  							Replicas: 1,
   685  							Template: v1.PodTemplateSpec{
   686  								ObjectMeta: metav1.ObjectMeta{
   687  									Labels: map[string]string{"name": "test"},
   688  								},
   689  								Spec: v1.PodSpec{
   690  									Containers: []v1.Container{
   691  										{
   692  											Name:  "fake-name",
   693  											Image: "busybox:1.24",
   694  										},
   695  									},
   696  								},
   697  							},
   698  						},
   699  					},
   700  					Policies: []v1alpha1.LifecyclePolicy{
   701  						{
   702  							Action: busv1alpha1.AbortJobAction,
   703  							ExitCode: func(i int32) *int32 {
   704  								return &i
   705  							}(int32(0)),
   706  						},
   707  					},
   708  				},
   709  			},
   710  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   711  			ret:            "0 is not a valid error code",
   712  			ExpectErr:      true,
   713  		},
   714  		// duplicate policy exit-code
   715  		{
   716  			Name: "duplicate-exitcode",
   717  			Job: v1alpha1.Job{
   718  				ObjectMeta: metav1.ObjectMeta{
   719  					Name:      "duplicate-exitcode",
   720  					Namespace: namespace,
   721  				},
   722  				Spec: v1alpha1.JobSpec{
   723  					MinAvailable: 1,
   724  					Queue:        "default",
   725  					Tasks: []v1alpha1.TaskSpec{
   726  						{
   727  							Name:     "task-1",
   728  							Replicas: 1,
   729  							Template: v1.PodTemplateSpec{
   730  								ObjectMeta: metav1.ObjectMeta{
   731  									Labels: map[string]string{"name": "test"},
   732  								},
   733  								Spec: v1.PodSpec{
   734  									Containers: []v1.Container{
   735  										{
   736  											Name:  "fake-name",
   737  											Image: "busybox:1.24",
   738  										},
   739  									},
   740  								},
   741  							},
   742  						},
   743  					},
   744  					Policies: []v1alpha1.LifecyclePolicy{
   745  						{
   746  							ExitCode: func(i int32) *int32 {
   747  								return &i
   748  							}(int32(1)),
   749  						},
   750  						{
   751  							ExitCode: func(i int32) *int32 {
   752  								return &i
   753  							}(int32(1)),
   754  						},
   755  					},
   756  				},
   757  			},
   758  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   759  			ret:            "duplicate exitCode 1",
   760  			ExpectErr:      true,
   761  		},
   762  		// Policy with any event and other events
   763  		{
   764  			Name: "job-policy-withExitCode",
   765  			Job: v1alpha1.Job{
   766  				ObjectMeta: metav1.ObjectMeta{
   767  					Name:      "job-policy-withExitCode",
   768  					Namespace: namespace,
   769  				},
   770  				Spec: v1alpha1.JobSpec{
   771  					MinAvailable: 1,
   772  					Queue:        "default",
   773  					Tasks: []v1alpha1.TaskSpec{
   774  						{
   775  							Name:     "task-1",
   776  							Replicas: 1,
   777  							Template: v1.PodTemplateSpec{
   778  								ObjectMeta: metav1.ObjectMeta{
   779  									Labels: map[string]string{"name": "test"},
   780  								},
   781  								Spec: v1.PodSpec{
   782  									Containers: []v1.Container{
   783  										{
   784  											Name:  "fake-name",
   785  											Image: "busybox:1.24",
   786  										},
   787  									},
   788  								},
   789  							},
   790  						},
   791  					},
   792  					Policies: []v1alpha1.LifecyclePolicy{
   793  						{
   794  							Event:  busv1alpha1.AnyEvent,
   795  							Action: busv1alpha1.AbortJobAction,
   796  						},
   797  						{
   798  							Event:  busv1alpha1.PodFailedEvent,
   799  							Action: busv1alpha1.RestartJobAction,
   800  						},
   801  					},
   802  				},
   803  			},
   804  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   805  			ret:            "if there's * here, no other policy should be here",
   806  			ExpectErr:      true,
   807  		},
   808  		// invalid mount volume
   809  		{
   810  			Name: "invalid-mount-volume",
   811  			Job: v1alpha1.Job{
   812  				ObjectMeta: metav1.ObjectMeta{
   813  					Name:      "invalid-mount-volume",
   814  					Namespace: namespace,
   815  				},
   816  				Spec: v1alpha1.JobSpec{
   817  					MinAvailable: 1,
   818  					Queue:        "default",
   819  					Tasks: []v1alpha1.TaskSpec{
   820  						{
   821  							Name:     "task-1",
   822  							Replicas: 1,
   823  							Template: v1.PodTemplateSpec{
   824  								ObjectMeta: metav1.ObjectMeta{
   825  									Labels: map[string]string{"name": "test"},
   826  								},
   827  								Spec: v1.PodSpec{
   828  									Containers: []v1.Container{
   829  										{
   830  											Name:  "fake-name",
   831  											Image: "busybox:1.24",
   832  										},
   833  									},
   834  								},
   835  							},
   836  						},
   837  					},
   838  					Policies: []v1alpha1.LifecyclePolicy{
   839  						{
   840  							Event:  busv1alpha1.AnyEvent,
   841  							Action: busv1alpha1.AbortJobAction,
   842  						},
   843  					},
   844  					Volumes: []v1alpha1.VolumeSpec{
   845  						{
   846  							MountPath: "",
   847  						},
   848  					},
   849  				},
   850  			},
   851  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   852  			ret:            " mountPath is required;",
   853  			ExpectErr:      true,
   854  		},
   855  		// duplicate mount volume
   856  		{
   857  			Name: "duplicate-mount-volume",
   858  			Job: v1alpha1.Job{
   859  				ObjectMeta: metav1.ObjectMeta{
   860  					Name:      "duplicate-mount-volume",
   861  					Namespace: namespace,
   862  				},
   863  				Spec: v1alpha1.JobSpec{
   864  					MinAvailable: 1,
   865  					Queue:        "default",
   866  					Tasks: []v1alpha1.TaskSpec{
   867  						{
   868  							Name:     "task-1",
   869  							Replicas: 1,
   870  							Template: v1.PodTemplateSpec{
   871  								ObjectMeta: metav1.ObjectMeta{
   872  									Labels: map[string]string{"name": "test"},
   873  								},
   874  								Spec: v1.PodSpec{
   875  									Containers: []v1.Container{
   876  										{
   877  											Name:  "fake-name",
   878  											Image: "busybox:1.24",
   879  										},
   880  									},
   881  								},
   882  							},
   883  						},
   884  					},
   885  					Policies: []v1alpha1.LifecyclePolicy{
   886  						{
   887  							Event:  busv1alpha1.AnyEvent,
   888  							Action: busv1alpha1.AbortJobAction,
   889  						},
   890  					},
   891  					Volumes: []v1alpha1.VolumeSpec{
   892  						{
   893  							MountPath:       "/var",
   894  							VolumeClaimName: "pvc1",
   895  						},
   896  						{
   897  							MountPath:       "/var",
   898  							VolumeClaimName: "pvc2",
   899  						},
   900  					},
   901  				},
   902  			},
   903  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   904  			ret:            " duplicated mountPath: /var;",
   905  			ExpectErr:      true,
   906  		},
   907  		{
   908  			Name: "volume without VolumeClaimName and VolumeClaim",
   909  			Job: v1alpha1.Job{
   910  				ObjectMeta: metav1.ObjectMeta{
   911  					Name:      "invalid-volume",
   912  					Namespace: namespace,
   913  				},
   914  				Spec: v1alpha1.JobSpec{
   915  					MinAvailable: 1,
   916  					Queue:        "default",
   917  					Tasks: []v1alpha1.TaskSpec{
   918  						{
   919  							Name:     "task-1",
   920  							Replicas: 1,
   921  							Template: v1.PodTemplateSpec{
   922  								ObjectMeta: metav1.ObjectMeta{
   923  									Labels: map[string]string{"name": "test"},
   924  								},
   925  								Spec: v1.PodSpec{
   926  									Containers: []v1.Container{
   927  										{
   928  											Name:  "fake-name",
   929  											Image: "busybox:1.24",
   930  										},
   931  									},
   932  								},
   933  							},
   934  						},
   935  					},
   936  					Policies: []v1alpha1.LifecyclePolicy{
   937  						{
   938  							Event:  busv1alpha1.AnyEvent,
   939  							Action: busv1alpha1.AbortJobAction,
   940  						},
   941  					},
   942  					Volumes: []v1alpha1.VolumeSpec{
   943  						{
   944  							MountPath: "/var",
   945  						},
   946  						{
   947  							MountPath: "/var",
   948  						},
   949  					},
   950  				},
   951  			},
   952  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   953  			ret:            " either VolumeClaim or VolumeClaimName must be specified;",
   954  			ExpectErr:      true,
   955  		},
   956  		// task Policy with any event and other events
   957  		{
   958  			Name: "taskpolicy-withAnyandOthrEvent",
   959  			Job: v1alpha1.Job{
   960  				ObjectMeta: metav1.ObjectMeta{
   961  					Name:      "taskpolicy-withAnyandOthrEvent",
   962  					Namespace: namespace,
   963  				},
   964  				Spec: v1alpha1.JobSpec{
   965  					MinAvailable: 1,
   966  					Queue:        "default",
   967  					Tasks: []v1alpha1.TaskSpec{
   968  						{
   969  							Name:     "task-1",
   970  							Replicas: 1,
   971  							Template: v1.PodTemplateSpec{
   972  								ObjectMeta: metav1.ObjectMeta{
   973  									Labels: map[string]string{"name": "test"},
   974  								},
   975  								Spec: v1.PodSpec{
   976  									Containers: []v1.Container{
   977  										{
   978  											Name:  "fake-name",
   979  											Image: "busybox:1.24",
   980  										},
   981  									},
   982  								},
   983  							},
   984  							Policies: []v1alpha1.LifecyclePolicy{
   985  								{
   986  									Event:  busv1alpha1.AnyEvent,
   987  									Action: busv1alpha1.AbortJobAction,
   988  								},
   989  								{
   990  									Event:  busv1alpha1.PodFailedEvent,
   991  									Action: busv1alpha1.RestartJobAction,
   992  								},
   993  							},
   994  						},
   995  					},
   996  				},
   997  			},
   998  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
   999  			ret:            "if there's * here, no other policy should be here",
  1000  			ExpectErr:      true,
  1001  		},
  1002  		// job with no queue created
  1003  		{
  1004  			Name: "job-with-noQueue",
  1005  			Job: v1alpha1.Job{
  1006  				ObjectMeta: metav1.ObjectMeta{
  1007  					Name:      "job-with-noQueue",
  1008  					Namespace: namespace,
  1009  				},
  1010  				Spec: v1alpha1.JobSpec{
  1011  					MinAvailable: 1,
  1012  					Queue:        "jobQueue",
  1013  					Tasks: []v1alpha1.TaskSpec{
  1014  						{
  1015  							Name:     "task-1",
  1016  							Replicas: 1,
  1017  							Template: v1.PodTemplateSpec{
  1018  								ObjectMeta: metav1.ObjectMeta{
  1019  									Labels: map[string]string{"name": "test"},
  1020  								},
  1021  								Spec: v1.PodSpec{
  1022  									Containers: []v1.Container{
  1023  										{
  1024  											Name:  "fake-name",
  1025  											Image: "busybox:1.24",
  1026  										},
  1027  									},
  1028  								},
  1029  							},
  1030  						},
  1031  					},
  1032  				},
  1033  			},
  1034  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
  1035  			ret:            "unable to find job queue",
  1036  			ExpectErr:      true,
  1037  		},
  1038  		{
  1039  			Name: "job with priviledged init container",
  1040  			Job: v1alpha1.Job{
  1041  				ObjectMeta: metav1.ObjectMeta{
  1042  					Name:      "valid-job",
  1043  					Namespace: namespace,
  1044  				},
  1045  				Spec: v1alpha1.JobSpec{
  1046  					MinAvailable: 1,
  1047  					Queue:        "default",
  1048  					Tasks: []v1alpha1.TaskSpec{
  1049  						{
  1050  							Name:     "task-1",
  1051  							Replicas: 1,
  1052  							Template: v1.PodTemplateSpec{
  1053  								ObjectMeta: metav1.ObjectMeta{
  1054  									Labels: map[string]string{"name": "test"},
  1055  								},
  1056  								Spec: v1.PodSpec{
  1057  									InitContainers: []v1.Container{
  1058  										{
  1059  											Name:  "init-fake-name",
  1060  											Image: "busybox:1.24",
  1061  											SecurityContext: &v1.SecurityContext{
  1062  												Privileged: &priviledged,
  1063  											},
  1064  										},
  1065  									},
  1066  									Containers: []v1.Container{
  1067  										{
  1068  											Name:  "fake-name",
  1069  											Image: "busybox:1.24",
  1070  										},
  1071  									},
  1072  								},
  1073  							},
  1074  						},
  1075  					},
  1076  				},
  1077  			},
  1078  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
  1079  			ret:            "",
  1080  			ExpectErr:      false,
  1081  		},
  1082  		{
  1083  			Name: "job with priviledged container",
  1084  			Job: v1alpha1.Job{
  1085  				ObjectMeta: metav1.ObjectMeta{
  1086  					Name:      "valid-job",
  1087  					Namespace: namespace,
  1088  				},
  1089  				Spec: v1alpha1.JobSpec{
  1090  					MinAvailable: 1,
  1091  					Queue:        "default",
  1092  					Tasks: []v1alpha1.TaskSpec{
  1093  						{
  1094  							Name:     "task-1",
  1095  							Replicas: 1,
  1096  							Template: v1.PodTemplateSpec{
  1097  								ObjectMeta: metav1.ObjectMeta{
  1098  									Labels: map[string]string{"name": "test"},
  1099  								},
  1100  								Spec: v1.PodSpec{
  1101  									Containers: []v1.Container{
  1102  										{
  1103  											Name:  "fake-name",
  1104  											Image: "busybox:1.24",
  1105  											SecurityContext: &v1.SecurityContext{
  1106  												Privileged: &priviledged,
  1107  											},
  1108  										},
  1109  									},
  1110  								},
  1111  							},
  1112  						},
  1113  					},
  1114  				},
  1115  			},
  1116  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
  1117  			ret:            "",
  1118  			ExpectErr:      false,
  1119  		},
  1120  		{
  1121  			Name: "job with valid task depends on",
  1122  			Job: v1alpha1.Job{
  1123  				ObjectMeta: metav1.ObjectMeta{
  1124  					Name:      "job-with-valid-task-depends-on",
  1125  					Namespace: namespace,
  1126  				},
  1127  				Spec: v1alpha1.JobSpec{
  1128  					MinAvailable: 1,
  1129  					Queue:        "default",
  1130  					Tasks: []v1alpha1.TaskSpec{
  1131  						{
  1132  							Name:     "t1",
  1133  							Replicas: 1,
  1134  							DependsOn: &v1alpha1.DependsOn{
  1135  								Name: []string{"t2"},
  1136  							},
  1137  							Template: v1.PodTemplateSpec{
  1138  								Spec: v1.PodSpec{
  1139  									Containers: []v1.Container{
  1140  										{
  1141  											Name:  "fake-name",
  1142  											Image: "busybox:1.24",
  1143  										},
  1144  									},
  1145  								},
  1146  							},
  1147  						},
  1148  						{
  1149  							Name:      "t2",
  1150  							Replicas:  1,
  1151  							DependsOn: nil,
  1152  							Template: v1.PodTemplateSpec{
  1153  								Spec: v1.PodSpec{
  1154  									Containers: []v1.Container{
  1155  										{
  1156  											Name:  "fake-name",
  1157  											Image: "busybox:1.24",
  1158  										},
  1159  									},
  1160  								},
  1161  							},
  1162  						},
  1163  					},
  1164  				},
  1165  			},
  1166  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
  1167  			ret:            "",
  1168  			ExpectErr:      false,
  1169  		},
  1170  		{
  1171  			Name: "job with invalid task depends on",
  1172  			Job: v1alpha1.Job{
  1173  				ObjectMeta: metav1.ObjectMeta{
  1174  					Name:      "job-with-invalid-task-depends-on",
  1175  					Namespace: namespace,
  1176  				},
  1177  				Spec: v1alpha1.JobSpec{
  1178  					MinAvailable: 1,
  1179  					Queue:        "default",
  1180  					Tasks: []v1alpha1.TaskSpec{
  1181  						{
  1182  							Name:     "t1",
  1183  							Replicas: 1,
  1184  							DependsOn: &v1alpha1.DependsOn{
  1185  								Name: []string{"t3"},
  1186  							},
  1187  							Template: v1.PodTemplateSpec{
  1188  								Spec: v1.PodSpec{
  1189  									Containers: []v1.Container{
  1190  										{
  1191  											Name:  "fake-name",
  1192  											Image: "busybox:1.24",
  1193  										},
  1194  									},
  1195  								},
  1196  							},
  1197  						},
  1198  						{
  1199  							Name:      "t2",
  1200  							Replicas:  1,
  1201  							DependsOn: nil,
  1202  							Template: v1.PodTemplateSpec{
  1203  								Spec: v1.PodSpec{
  1204  									Containers: []v1.Container{
  1205  										{
  1206  											Name:  "fake-name",
  1207  											Image: "busybox:1.24",
  1208  										},
  1209  									},
  1210  								},
  1211  							},
  1212  						},
  1213  					},
  1214  				},
  1215  			},
  1216  			reviewResponse: admissionv1.AdmissionResponse{Allowed: true},
  1217  			ret:            "job has dependencies between tasks, but doesn't form a directed acyclic graph(DAG)",
  1218  			ExpectErr:      true,
  1219  		},
  1220  	}
  1221  
  1222  	for _, testCase := range testCases {
  1223  		t.Run(testCase.Name, func(t *testing.T) {
  1224  			defaultqueue := schedulingv1beta2.Queue{
  1225  				ObjectMeta: metav1.ObjectMeta{
  1226  					Name: "default",
  1227  				},
  1228  				Spec: schedulingv1beta2.QueueSpec{
  1229  					Weight: 1,
  1230  				},
  1231  				Status: schedulingv1beta2.QueueStatus{
  1232  					State: schedulingv1beta2.QueueStateOpen,
  1233  				},
  1234  			}
  1235  			// create fake volcano clientset
  1236  			config.VolcanoClient = fakeclient.NewSimpleClientset()
  1237  
  1238  			//create default queue
  1239  			_, err := config.VolcanoClient.SchedulingV1beta1().Queues().Create(context.TODO(), &defaultqueue, metav1.CreateOptions{})
  1240  			if err != nil {
  1241  				t.Error("Queue Creation Failed")
  1242  			}
  1243  
  1244  			ret := validateJobCreate(&testCase.Job, &testCase.reviewResponse)
  1245  			//fmt.Printf("test-case name:%s, ret:%v  testCase.reviewResponse:%v \n", testCase.Name, ret,testCase.reviewResponse)
  1246  			if testCase.ExpectErr == true && ret == "" {
  1247  				t.Errorf("Expect error msg :%s, but got nil.", testCase.ret)
  1248  			}
  1249  			if testCase.ExpectErr == true && testCase.reviewResponse.Allowed != false {
  1250  				t.Errorf("Expect Allowed as false but got true.")
  1251  			}
  1252  			if testCase.ExpectErr == true && !strings.Contains(ret, testCase.ret) {
  1253  				t.Errorf("Expect error msg :%s, but got diff error %v", testCase.ret, ret)
  1254  			}
  1255  
  1256  			if testCase.ExpectErr == false && ret != "" {
  1257  				t.Errorf("Expect no error, but got error %v", ret)
  1258  			}
  1259  			if testCase.ExpectErr == false && testCase.reviewResponse.Allowed != true {
  1260  				t.Errorf("Expect Allowed as true but got false. %v", testCase.reviewResponse)
  1261  			}
  1262  		})
  1263  	}
  1264  }
  1265  
  1266  func TestValidateJobUpdate(t *testing.T) {
  1267  	testCases := []struct {
  1268  		name           string
  1269  		replicas       int32
  1270  		minAvailable   int32
  1271  		addTask        bool
  1272  		mutateTaskName bool
  1273  		mutateSpec     bool
  1274  		expectErr      bool
  1275  	}{
  1276  		{
  1277  			name:           "scale up",
  1278  			replicas:       6,
  1279  			minAvailable:   5,
  1280  			addTask:        false,
  1281  			mutateTaskName: false,
  1282  			mutateSpec:     false,
  1283  			expectErr:      false,
  1284  		},
  1285  		{
  1286  			name:           "invalid scale down with replicas less than minAvailable",
  1287  			replicas:       4,
  1288  			minAvailable:   5,
  1289  			addTask:        false,
  1290  			mutateTaskName: false,
  1291  			mutateSpec:     false,
  1292  			expectErr:      true,
  1293  		},
  1294  		{
  1295  			name:           "scale down",
  1296  			replicas:       4,
  1297  			minAvailable:   3,
  1298  			addTask:        false,
  1299  			mutateTaskName: false,
  1300  			mutateSpec:     false,
  1301  			expectErr:      false,
  1302  		},
  1303  		{
  1304  			name:           "invalid minAvailable",
  1305  			replicas:       4,
  1306  			minAvailable:   -1,
  1307  			addTask:        false,
  1308  			mutateTaskName: false,
  1309  			mutateSpec:     false,
  1310  			expectErr:      true,
  1311  		},
  1312  		{
  1313  			name:           "invalid add task",
  1314  			replicas:       4,
  1315  			minAvailable:   5,
  1316  			addTask:        true,
  1317  			mutateTaskName: false,
  1318  			mutateSpec:     false,
  1319  			expectErr:      true,
  1320  		},
  1321  		{
  1322  			name:           "invalid mutate task's fields other than replicas",
  1323  			replicas:       5,
  1324  			minAvailable:   5,
  1325  			addTask:        false,
  1326  			mutateTaskName: true,
  1327  			mutateSpec:     false,
  1328  			expectErr:      true,
  1329  		},
  1330  		{
  1331  			name:           "invalid mutate job's spec other than minAvailable",
  1332  			replicas:       5,
  1333  			minAvailable:   5,
  1334  			addTask:        false,
  1335  			mutateTaskName: false,
  1336  			mutateSpec:     true,
  1337  			expectErr:      true,
  1338  		},
  1339  	}
  1340  
  1341  	for _, tc := range testCases {
  1342  		t.Run(tc.name, func(t *testing.T) {
  1343  			old := newJob()
  1344  			new := newJob()
  1345  			new.ResourceVersion = "502593"
  1346  			new.Status.Succeeded = 2
  1347  
  1348  			new.Spec.MinAvailable = tc.minAvailable
  1349  			new.Spec.Tasks[0].Replicas = tc.replicas
  1350  
  1351  			if tc.addTask {
  1352  				new.Spec.Tasks = append(new.Spec.Tasks, v1alpha1.TaskSpec{
  1353  					Name:     "task-2",
  1354  					Replicas: 5,
  1355  					Template: v1.PodTemplateSpec{
  1356  						ObjectMeta: metav1.ObjectMeta{
  1357  							Labels: map[string]string{"name": "test"},
  1358  						},
  1359  						Spec: v1.PodSpec{
  1360  							Containers: []v1.Container{
  1361  								{
  1362  									Name:  "fake-name",
  1363  									Image: "busybox:1.24",
  1364  								},
  1365  							},
  1366  						},
  1367  					},
  1368  				})
  1369  			}
  1370  			if tc.mutateTaskName {
  1371  				new.Spec.Tasks[0].Name = "mutated-name"
  1372  			}
  1373  			if tc.mutateSpec {
  1374  				new.Spec.Queue = "mutated-queue"
  1375  			}
  1376  
  1377  			err := validateJobUpdate(old, new)
  1378  			if err != nil && !tc.expectErr {
  1379  				t.Errorf("Expected no error, but got: %v", err)
  1380  			}
  1381  			if err == nil && tc.expectErr {
  1382  				t.Errorf("Expected error, but got none")
  1383  			}
  1384  		})
  1385  	}
  1386  
  1387  }
  1388  
  1389  func newJob() *v1alpha1.Job {
  1390  	return &v1alpha1.Job{
  1391  		ObjectMeta: metav1.ObjectMeta{
  1392  			Name:      "valid-job",
  1393  			Namespace: "default",
  1394  		},
  1395  		Spec: v1alpha1.JobSpec{
  1396  			MinAvailable: 5,
  1397  			Queue:        "default",
  1398  			Tasks: []v1alpha1.TaskSpec{
  1399  				{
  1400  					Name:     "task-1",
  1401  					Replicas: 5,
  1402  					Template: v1.PodTemplateSpec{
  1403  						ObjectMeta: metav1.ObjectMeta{
  1404  							Labels: map[string]string{"name": "test"},
  1405  						},
  1406  						Spec: v1.PodSpec{
  1407  							Containers: []v1.Container{
  1408  								{
  1409  									Name:  "fake-name",
  1410  									Image: "busybox:1.24",
  1411  								},
  1412  							},
  1413  						},
  1414  					},
  1415  				},
  1416  			},
  1417  			Policies: []v1alpha1.LifecyclePolicy{
  1418  				{
  1419  					Event:  busv1alpha1.PodEvictedEvent,
  1420  					Action: busv1alpha1.RestartTaskAction,
  1421  				},
  1422  			},
  1423  		},
  1424  	}
  1425  }
  1426  
  1427  func TestValidateTaskTopoPolicy(t *testing.T) {
  1428  	testCases := []struct {
  1429  		name     string
  1430  		taskSpec v1alpha1.TaskSpec
  1431  		expect   string
  1432  	}{
  1433  		{
  1434  			name: "test-1",
  1435  			taskSpec: v1alpha1.TaskSpec{
  1436  				Name:           "task-1",
  1437  				Replicas:       5,
  1438  				TopologyPolicy: v1alpha1.Restricted,
  1439  				Template: v1.PodTemplateSpec{
  1440  					ObjectMeta: metav1.ObjectMeta{
  1441  						Labels: map[string]string{"name": "test"},
  1442  					},
  1443  					Spec: v1.PodSpec{
  1444  						Containers: []v1.Container{
  1445  							{
  1446  								Resources: v1.ResourceRequirements{
  1447  									Limits: v1.ResourceList{
  1448  										v1.ResourceCPU:    *resource.NewQuantity(1, ""),
  1449  										v1.ResourceMemory: *resource.NewQuantity(2000, resource.BinarySI),
  1450  									},
  1451  								},
  1452  							},
  1453  						},
  1454  					},
  1455  				},
  1456  			},
  1457  			expect: "",
  1458  		},
  1459  		{
  1460  			name: "test-2",
  1461  			taskSpec: v1alpha1.TaskSpec{
  1462  				Name:           "task-2",
  1463  				TopologyPolicy: v1alpha1.Restricted,
  1464  				Template: v1.PodTemplateSpec{
  1465  					Spec: v1.PodSpec{
  1466  						Containers: []v1.Container{
  1467  							{
  1468  								Resources: v1.ResourceRequirements{
  1469  									Limits: v1.ResourceList{
  1470  										v1.ResourceCPU:    *resource.NewMilliQuantity(500, resource.DecimalSI),
  1471  										v1.ResourceMemory: *resource.NewQuantity(2000, resource.BinarySI),
  1472  									},
  1473  								},
  1474  							},
  1475  						},
  1476  					},
  1477  				},
  1478  			},
  1479  			expect: "the cpu request isn't  an integer",
  1480  		},
  1481  	}
  1482  
  1483  	for _, testcase := range testCases {
  1484  		msg := validateTaskTopoPolicy(testcase.taskSpec, 0)
  1485  		if !strings.Contains(msg, testcase.expect) {
  1486  			t.Errorf("%s failed.", testcase.name)
  1487  		}
  1488  	}
  1489  }