mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
implement inter pod topological affinity and anti-affinity
This commit is contained in:
@@ -34,6 +34,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
unversionedvalidation "k8s.io/kubernetes/pkg/api/unversioned/validation"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/capabilities"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
@@ -74,26 +75,6 @@ var BannedOwners = map[unversioned.GroupVersionKind]struct{}{
|
||||
v1.SchemeGroupVersion.WithKind("Event"): {},
|
||||
}
|
||||
|
||||
func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if !validation.IsQualifiedName(labelName) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, labelName, qualifiedNameErrorMsg))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateLabels validates that a set of labels are correctly defined.
|
||||
func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for k, v := range labels {
|
||||
allErrs = append(allErrs, ValidateLabelName(k, fldPath)...)
|
||||
if !validation.IsValidLabelValue(v) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, v, labelValueErrorMsg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateHasLabel requires that api.ObjectMeta has a Label with key and expectedValue
|
||||
func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
@@ -361,7 +342,7 @@ func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn Val
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...)
|
||||
allErrs = append(allErrs, ValidateLabels(meta.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
||||
|
||||
@@ -416,7 +397,7 @@ func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.P
|
||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...)
|
||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...)
|
||||
|
||||
allErrs = append(allErrs, ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
||||
|
||||
@@ -1448,7 +1429,7 @@ func ValidatePodSpec(spec *api.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes, fldPath.Child("containers"))...)
|
||||
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
||||
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...)
|
||||
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
|
||||
if len(spec.ServiceAccountName) > 0 {
|
||||
@@ -1500,7 +1481,7 @@ func ValidateNodeSelectorRequirement(rq api.NodeSelectorRequirement, fldPath *fi
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator"))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateLabelName(rq.Key, fldPath.Child("key"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@@ -1547,6 +1528,87 @@ func ValidatePreferredSchedulingTerms(terms []api.PreferredSchedulingTerm, fldPa
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data
|
||||
func validatePodAffinityTerm(podAffinityTerm api.PodAffinityTerm, allowEmptyTopologyKey bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, fldPath.Child("matchExpressions"))...)
|
||||
for _, name := range podAffinityTerm.Namespaces {
|
||||
if ok, _ := ValidateNamespaceName(name, false); !ok {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, DNS1123LabelErrorMsg))
|
||||
}
|
||||
}
|
||||
if !allowEmptyTopologyKey && len(podAffinityTerm.TopologyKey) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can only be empty for PreferredDuringScheduling pod anti affinity"))
|
||||
}
|
||||
if len(podAffinityTerm.TopologyKey) != 0 {
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data
|
||||
func validatePodAffinityTerms(podAffinityTerms []api.PodAffinityTerm, allowEmptyTopologyKey bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, podAffinityTerm := range podAffinityTerms {
|
||||
allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, allowEmptyTopologyKey, fldPath.Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data
|
||||
func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []api.WeightedPodAffinityTerm, allowEmptyTopologyKey bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for j, weightedTerm := range weightedPodAffinityTerms {
|
||||
if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100"))
|
||||
}
|
||||
allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, allowEmptyTopologyKey, fldPath.Index(j).Child("podAffinityTerm"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data
|
||||
func validatePodAntiAffinity(podAntiAffinity *api.PodAntiAffinity, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
// if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||
// allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||
//}
|
||||
if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
// empty topologyKey is not allowed for hard pod anti-affinity
|
||||
allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, false,
|
||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
// empty topologyKey is allowed for soft pod anti-affinity
|
||||
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, true,
|
||||
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodAffinity tests that the specified podAffinity fields have valid data
|
||||
func validatePodAffinity(podAffinity *api.PodAffinity, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
// if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||
// allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||
//}
|
||||
if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
// empty topologyKey is not allowed for hard pod affinity
|
||||
allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, false,
|
||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
// empty topologyKey is not allowed for soft pod affinity
|
||||
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, false,
|
||||
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateAffinityInPodAnnotations tests that the serialized Affinity in Pod.Annotations has valid data
|
||||
func ValidateAffinityInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
@@ -1557,23 +1619,29 @@ func ValidateAffinityInPodAnnotations(annotations map[string]string, fldPath *fi
|
||||
return allErrs
|
||||
}
|
||||
|
||||
affinityFldPath := fldPath.Child(api.AffinityAnnotationKey)
|
||||
if affinity.NodeAffinity != nil {
|
||||
na := affinity.NodeAffinity
|
||||
|
||||
naFldPath := affinityFldPath.Child("nodeAffinity")
|
||||
// TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
// if na.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||
// allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||
// allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, naFldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||
// }
|
||||
|
||||
if na.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, naFldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
|
||||
if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 {
|
||||
allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
|
||||
allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, naFldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
}
|
||||
if affinity.PodAffinity != nil {
|
||||
allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, affinityFldPath.Child("podAffinity"))...)
|
||||
}
|
||||
if affinity.PodAntiAffinity != nil {
|
||||
allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, affinityFldPath.Child("podAntiAffinity"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@@ -1749,7 +1817,7 @@ func ValidateService(service *api.Service) field.ErrorList {
|
||||
}
|
||||
|
||||
if service.Spec.Selector != nil {
|
||||
allErrs = append(allErrs, ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...)
|
||||
}
|
||||
|
||||
if len(service.Spec.SessionAffinity) == 0 {
|
||||
@@ -1968,7 +2036,7 @@ func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec, fldP
|
||||
// ValidatePodTemplateSpec validates the spec of a pod template
|
||||
func ValidatePodTemplateSpec(spec *api.PodTemplateSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, ValidateLabels(spec.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...)
|
||||
|
||||
@@ -282,67 +282,6 @@ func TestValidateObjectMetaUpdateDisallowsUpdatingFinalizers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLabels(t *testing.T) {
|
||||
successCases := []map[string]string{
|
||||
{"simple": "bar"},
|
||||
{"now-with-dashes": "bar"},
|
||||
{"1-starts-with-num": "bar"},
|
||||
{"1234": "bar"},
|
||||
{"simple/simple": "bar"},
|
||||
{"now-with-dashes/simple": "bar"},
|
||||
{"now-with-dashes/now-with-dashes": "bar"},
|
||||
{"now.with.dots/simple": "bar"},
|
||||
{"now-with.dashes-and.dots/simple": "bar"},
|
||||
{"1-num.2-num/3-num": "bar"},
|
||||
{"1234/5678": "bar"},
|
||||
{"1.2.3.4/5678": "bar"},
|
||||
{"UpperCaseAreOK123": "bar"},
|
||||
{"goodvalue": "123_-.BaR"},
|
||||
}
|
||||
for i := range successCases {
|
||||
errs := ValidateLabels(successCases[i], field.NewPath("field"))
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("case[%d] expected success, got %#v", i, errs)
|
||||
}
|
||||
}
|
||||
|
||||
labelNameErrorCases := []map[string]string{
|
||||
{"nospecialchars^=@": "bar"},
|
||||
{"cantendwithadash-": "bar"},
|
||||
{"only/one/slash": "bar"},
|
||||
{strings.Repeat("a", 254): "bar"},
|
||||
}
|
||||
for i := range labelNameErrorCases {
|
||||
errs := ValidateLabels(labelNameErrorCases[i], field.NewPath("field"))
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("case[%d] expected failure", i)
|
||||
} else {
|
||||
detail := errs[0].Detail
|
||||
if detail != qualifiedNameErrorMsg {
|
||||
t.Errorf("error detail %s should be equal %s", detail, qualifiedNameErrorMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labelValueErrorCases := []map[string]string{
|
||||
{"toolongvalue": strings.Repeat("a", 64)},
|
||||
{"backslashesinvalue": "some\\bad\\value"},
|
||||
{"nocommasallowed": "bad,value"},
|
||||
{"strangecharsinvalue": "?#$notsogood"},
|
||||
}
|
||||
for i := range labelValueErrorCases {
|
||||
errs := ValidateLabels(labelValueErrorCases[i], field.NewPath("field"))
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("case[%d] expected failure", i)
|
||||
} else {
|
||||
detail := errs[0].Detail
|
||||
if detail != labelValueErrorMsg {
|
||||
t.Errorf("error detail %s should be equal %s", detail, labelValueErrorMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAnnotations(t *testing.T) {
|
||||
successCases := []map[string]string{
|
||||
{"simple": "bar"},
|
||||
@@ -1969,52 +1908,6 @@ func TestValidatePod(t *testing.T) {
|
||||
NodeName: "foobar",
|
||||
},
|
||||
},
|
||||
{ // Serialized affinity requirements in annotations.
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
// TODO: Uncomment and move this block into Annotations map once
|
||||
// RequiredDuringSchedulingRequiredDuringExecution is implemented
|
||||
// "requiredDuringSchedulingRequiredDuringExecution": {
|
||||
// "nodeSelectorTerms": [{
|
||||
// "matchExpressions": [{
|
||||
// "key": "key1",
|
||||
// "operator": "Exists"
|
||||
// }]
|
||||
// }]
|
||||
// },
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"nodeAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": {
|
||||
"nodeSelectorTerms": [{
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
}]
|
||||
},
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [
|
||||
{
|
||||
"weight": 10,
|
||||
"preference": {"matchExpressions": [
|
||||
{
|
||||
"key": "foo",
|
||||
"operator": "In", "values": ["bar"]
|
||||
}
|
||||
]}
|
||||
}
|
||||
]
|
||||
}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, pod := range successCases {
|
||||
if errs := ValidatePod(&pod); len(errs) != 0 {
|
||||
@@ -2059,7 +1952,177 @@ func TestValidatePod(t *testing.T) {
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
},
|
||||
},
|
||||
"invalid json of affinity in pod annotations": {
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidatePod(&v); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAffinity(t *testing.T) {
|
||||
successCases := []api.Pod{
|
||||
{ // Serialized affinity requirements in annotations.
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
// TODO: Uncomment and move this block into Annotations map once
|
||||
// RequiredDuringSchedulingRequiredDuringExecution is implemented
|
||||
// "requiredDuringSchedulingRequiredDuringExecution": {
|
||||
// "nodeSelectorTerms": [{
|
||||
// "matchExpressions": [{
|
||||
// "key": "key1",
|
||||
// "operator": "Exists"
|
||||
// }]
|
||||
// }]
|
||||
// },
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"nodeAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": {
|
||||
"nodeSelectorTerms": [{
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
}]
|
||||
},
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [
|
||||
{
|
||||
"weight": 10,
|
||||
"preference": {"matchExpressions": [
|
||||
{
|
||||
"key": "foo",
|
||||
"operator": "In", "values": ["bar"]
|
||||
}
|
||||
]}
|
||||
}
|
||||
]
|
||||
}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
{ // Serialized pod affinity in affinity requirements in annotations.
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
// TODO: Uncomment and move this block into Annotations map once
|
||||
// RequiredDuringSchedulingRequiredDuringExecution is implemented
|
||||
// "requiredDuringSchedulingRequiredDuringExecution": [{
|
||||
// "labelSelector": {
|
||||
// "matchExpressions": [{
|
||||
// "key": "key2",
|
||||
// "operator": "In",
|
||||
// "values": ["value1", "value2"]
|
||||
// }]
|
||||
// },
|
||||
// "namespaces":["ns"],
|
||||
// "topologyKey": "zone"
|
||||
// }]
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"topologyKey": "zone",
|
||||
"namespaces": ["ns"]
|
||||
}],
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm": {
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "NotIn",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
{ // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations.
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
// TODO: Uncomment and move this block into Annotations map once
|
||||
// RequiredDuringSchedulingRequiredDuringExecution is implemented
|
||||
// "requiredDuringSchedulingRequiredDuringExecution": [{
|
||||
// "labelSelector": {
|
||||
// "matchExpressions": [{
|
||||
// "key": "key2",
|
||||
// "operator": "In",
|
||||
// "values": ["value1", "value2"]
|
||||
// }]
|
||||
// },
|
||||
// "namespaces":["ns"],
|
||||
// "topologyKey": "zone"
|
||||
// }]
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "Exists"
|
||||
}]
|
||||
},
|
||||
"topologyKey": "zone",
|
||||
"namespaces": ["ns"]
|
||||
}],
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm": {
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "DoesNotExist"
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, pod := range successCases {
|
||||
if errs := ValidatePod(&pod); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]api.Pod{
|
||||
"invalid json of node affinity in pod annotations": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@@ -2077,7 +2140,7 @@ func TestValidatePod(t *testing.T) {
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid node selector requirement in affinity in pod annotations, operator can't be null": {
|
||||
"invalid node selector requirement in node affinity in pod annotations, operator can't be null": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@@ -2098,7 +2161,7 @@ func TestValidatePod(t *testing.T) {
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid preferredSchedulingTerm in affinity in pod annotations, weight should be in range 1-100": {
|
||||
"invalid preferredSchedulingTerm in node affinity in pod annotations, weight should be in range 1-100": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@@ -2164,6 +2227,209 @@ func TestValidatePod(t *testing.T) {
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 109,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "NotIn",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "Exists",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, name space shouldbe valid": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "Exists",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["INVALID_NAMESPACE"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid labelOperator in preferredDuringSchedulingIgnoredDuringExecution in podantiaffinity annotations, labelOperator should be proper": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "WrongOp",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": "region"
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid pod affinity, empty topologyKey is not allowed for hard pod affinity": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": ""
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": ""
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"invalid pod anti-affinity, empty topologyKey is not allowed for soft pod affinity": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 10,
|
||||
"podAffinityTerm":
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "key2",
|
||||
"operator": "In",
|
||||
"values": ["value1", "value2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": ["ns"],
|
||||
"topologyKey": ""
|
||||
}
|
||||
}]}}`,
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidatePod(&v); len(errs) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user