mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Add support for pre-allocated hugepages with 2 sizes
Remove the validation for pre-allocated hugepages on node level. Validation is currently the only thing making it impossible to use pre-allocated huge pages in more than one size. We have now quite a few reports from real users that this feature is welcome.
This commit is contained in:
		| @@ -4424,8 +4424,15 @@ func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *fie | |||||||
| 	return allErrs | 	return allErrs | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NodeValidationOptions contains the different settings for node validation | ||||||
|  | type NodeValidationOptions struct { | ||||||
|  | 	// Should node a spec containing more than one huge page resource (with different sizes) | ||||||
|  | 	// with pre-allocated memory trigger validation errors | ||||||
|  | 	ValidateSingleHugePageResource bool | ||||||
|  | } | ||||||
|  |  | ||||||
| // ValidateNode tests if required fields in the node are set. | // ValidateNode tests if required fields in the node are set. | ||||||
| func ValidateNode(node *core.Node) field.ErrorList { | func ValidateNode(node *core.Node, opts NodeValidationOptions) field.ErrorList { | ||||||
| 	fldPath := field.NewPath("metadata") | 	fldPath := field.NewPath("metadata") | ||||||
| 	allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) | 	allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) | ||||||
| 	allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) | 	allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) | ||||||
| @@ -4436,7 +4443,7 @@ func ValidateNode(node *core.Node) field.ErrorList { | |||||||
| 	// Only validate spec. | 	// Only validate spec. | ||||||
| 	// All status fields are optional and can be updated later. | 	// All status fields are optional and can be updated later. | ||||||
| 	// That said, if specified, we need to ensure they are valid. | 	// That said, if specified, we need to ensure they are valid. | ||||||
| 	allErrs = append(allErrs, ValidateNodeResources(node)...) | 	allErrs = append(allErrs, ValidateNodeResources(node, opts)...) | ||||||
|  |  | ||||||
| 	// validate PodCIDRS only if we need to | 	// validate PodCIDRS only if we need to | ||||||
| 	if len(node.Spec.PodCIDRs) > 0 { | 	if len(node.Spec.PodCIDRs) > 0 { | ||||||
| @@ -4476,13 +4483,33 @@ func ValidateNode(node *core.Node) field.ErrorList { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ValidateNodeResources is used to make sure a node has valid capacity and allocatable values. | // ValidateNodeResources is used to make sure a node has valid capacity and allocatable values. | ||||||
| func ValidateNodeResources(node *core.Node) field.ErrorList { | func ValidateNodeResources(node *core.Node, opts NodeValidationOptions) field.ErrorList { | ||||||
|  | 	allErrs := field.ErrorList{} | ||||||
|  | 	if opts.ValidateSingleHugePageResource { | ||||||
|  | 		allErrs = append(allErrs, ValidateNodeSingleHugePageResources(node)...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Validate resource quantities in capacity. | ||||||
|  | 	for k, v := range node.Status.Capacity { | ||||||
|  | 		resPath := field.NewPath("status", "capacity", string(k)) | ||||||
|  | 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Validate resource quantities in allocatable. | ||||||
|  | 	for k, v := range node.Status.Allocatable { | ||||||
|  | 		resPath := field.NewPath("status", "allocatable", string(k)) | ||||||
|  | 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) | ||||||
|  | 	} | ||||||
|  | 	return allErrs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ValidateNodeHugePageResources is used to make sure a node has valid capacity and allocatable values for the huge page resources. | ||||||
|  | func ValidateNodeSingleHugePageResources(node *core.Node) field.ErrorList { | ||||||
| 	allErrs := field.ErrorList{} | 	allErrs := field.ErrorList{} | ||||||
| 	// Validate resource quantities in capacity. | 	// Validate resource quantities in capacity. | ||||||
| 	hugePageSizes := sets.NewString() | 	hugePageSizes := sets.NewString() | ||||||
| 	for k, v := range node.Status.Capacity { | 	for k, v := range node.Status.Capacity { | ||||||
| 		resPath := field.NewPath("status", "capacity", string(k)) | 		resPath := field.NewPath("status", "capacity", string(k)) | ||||||
| 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) |  | ||||||
| 		// track any huge page size that has a positive value | 		// track any huge page size that has a positive value | ||||||
| 		if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { | 		if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { | ||||||
| 			hugePageSizes.Insert(string(k)) | 			hugePageSizes.Insert(string(k)) | ||||||
| @@ -4495,7 +4522,6 @@ func ValidateNodeResources(node *core.Node) field.ErrorList { | |||||||
| 	hugePageSizes = sets.NewString() | 	hugePageSizes = sets.NewString() | ||||||
| 	for k, v := range node.Status.Allocatable { | 	for k, v := range node.Status.Allocatable { | ||||||
| 		resPath := field.NewPath("status", "allocatable", string(k)) | 		resPath := field.NewPath("status", "allocatable", string(k)) | ||||||
| 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) |  | ||||||
| 		// track any huge page size that has a positive value | 		// track any huge page size that has a positive value | ||||||
| 		if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { | 		if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { | ||||||
| 			hugePageSizes.Insert(string(k)) | 			hugePageSizes.Insert(string(k)) | ||||||
| @@ -4508,7 +4534,7 @@ func ValidateNodeResources(node *core.Node) field.ErrorList { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ValidateNodeUpdate tests to make sure a node update can be applied.  Modifies oldNode. | // ValidateNodeUpdate tests to make sure a node update can be applied.  Modifies oldNode. | ||||||
| func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { | func ValidateNodeUpdate(node, oldNode *core.Node, opts NodeValidationOptions) field.ErrorList { | ||||||
| 	fldPath := field.NewPath("metadata") | 	fldPath := field.NewPath("metadata") | ||||||
| 	allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath) | 	allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath) | ||||||
| 	allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) | 	allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) | ||||||
| @@ -4519,7 +4545,7 @@ func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { | |||||||
| 	// 	allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty")) | 	// 	allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty")) | ||||||
| 	// } | 	// } | ||||||
|  |  | ||||||
| 	allErrs = append(allErrs, ValidateNodeResources(node)...) | 	allErrs = append(allErrs, ValidateNodeResources(node, opts)...) | ||||||
|  |  | ||||||
| 	// Validate no duplicate addresses in node status. | 	// Validate no duplicate addresses in node status. | ||||||
| 	addresses := make(map[core.NodeAddress]bool) | 	addresses := make(map[core.NodeAddress]bool) | ||||||
|   | |||||||
| @@ -10812,6 +10812,9 @@ func TestValidateReplicationController(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestValidateNode(t *testing.T) { | func TestValidateNode(t *testing.T) { | ||||||
|  | 	opts := NodeValidationOptions{ | ||||||
|  | 		ValidateSingleHugePageResource: true, | ||||||
|  | 	} | ||||||
| 	validSelector := map[string]string{"a": "b"} | 	validSelector := map[string]string{"a": "b"} | ||||||
| 	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} | 	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} | ||||||
| 	successCases := []core.Node{ | 	successCases := []core.Node{ | ||||||
| @@ -10918,7 +10921,7 @@ func TestValidateNode(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	for _, successCase := range successCases { | 	for _, successCase := range successCases { | ||||||
| 		if errs := ValidateNode(&successCase); len(errs) != 0 { | 		if errs := ValidateNode(&successCase, opts); len(errs) != 0 { | ||||||
| 			t.Errorf("expected success: %v", errs) | 			t.Errorf("expected success: %v", errs) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -11142,7 +11145,7 @@ func TestValidateNode(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	for k, v := range errorCases { | 	for k, v := range errorCases { | ||||||
| 		errs := ValidateNode(&v) | 		errs := ValidateNode(&v, opts) | ||||||
| 		if len(errs) == 0 { | 		if len(errs) == 0 { | ||||||
| 			t.Errorf("expected failure for %s", k) | 			t.Errorf("expected failure for %s", k) | ||||||
| 		} | 		} | ||||||
| @@ -11169,7 +11172,134 @@ func TestValidateNode(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestNodeValidationOptions(t *testing.T) { | ||||||
|  | 	updateTests := []struct { | ||||||
|  | 		oldNode core.Node | ||||||
|  | 		node    core.Node | ||||||
|  | 		opts    NodeValidationOptions | ||||||
|  | 		valid   bool | ||||||
|  | 	}{ | ||||||
|  | 		{core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, NodeValidationOptions{ValidateSingleHugePageResource: true}, false}, | ||||||
|  | 		{core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, NodeValidationOptions{ValidateSingleHugePageResource: true}, false}, | ||||||
|  | 		{core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "not-validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "not-validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, NodeValidationOptions{ValidateSingleHugePageResource: false}, true}, | ||||||
|  | 	} | ||||||
|  | 	for i, test := range updateTests { | ||||||
|  | 		test.oldNode.ObjectMeta.ResourceVersion = "1" | ||||||
|  | 		test.node.ObjectMeta.ResourceVersion = "1" | ||||||
|  | 		errs := ValidateNodeUpdate(&test.node, &test.oldNode, test.opts) | ||||||
|  | 		if test.valid && len(errs) > 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected error: %v", i, errs) | ||||||
|  | 			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) | ||||||
|  | 		} | ||||||
|  | 		if !test.valid && len(errs) == 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected non-error", i) | ||||||
|  | 			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	nodeTests := []struct { | ||||||
|  | 		node  core.Node | ||||||
|  | 		opts  NodeValidationOptions | ||||||
|  | 		valid bool | ||||||
|  | 	}{ | ||||||
|  | 		{core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, NodeValidationOptions{ValidateSingleHugePageResource: true}, false}, | ||||||
|  | 		{core.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "not-validate-single-hugepages", | ||||||
|  | 			}, | ||||||
|  | 			Status: core.NodeStatus{ | ||||||
|  | 				Capacity: core.ResourceList{ | ||||||
|  | 					core.ResourceName("hugepages-2Mi"):  resource.MustParse("1Gi"), | ||||||
|  | 					core.ResourceName("hugepages-1Gi"):  resource.MustParse("2Gi"), | ||||||
|  | 					core.ResourceName("hugepages-64Ki"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, NodeValidationOptions{ValidateSingleHugePageResource: false}, true}, | ||||||
|  | 	} | ||||||
|  | 	for i, test := range nodeTests { | ||||||
|  | 		test.node.ObjectMeta.ResourceVersion = "1" | ||||||
|  | 		errs := ValidateNode(&test.node, test.opts) | ||||||
|  | 		if test.valid && len(errs) > 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected error: %v", i, errs) | ||||||
|  | 		} | ||||||
|  | 		if !test.valid && len(errs) == 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected non-error", i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| func TestValidateNodeUpdate(t *testing.T) { | func TestValidateNodeUpdate(t *testing.T) { | ||||||
|  | 	opts := NodeValidationOptions{ | ||||||
|  | 		ValidateSingleHugePageResource: true, | ||||||
|  | 	} | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		oldNode core.Node | 		oldNode core.Node | ||||||
| 		node    core.Node | 		node    core.Node | ||||||
| @@ -11593,7 +11723,7 @@ func TestValidateNodeUpdate(t *testing.T) { | |||||||
| 	for i, test := range tests { | 	for i, test := range tests { | ||||||
| 		test.oldNode.ObjectMeta.ResourceVersion = "1" | 		test.oldNode.ObjectMeta.ResourceVersion = "1" | ||||||
| 		test.node.ObjectMeta.ResourceVersion = "1" | 		test.node.ObjectMeta.ResourceVersion = "1" | ||||||
| 		errs := ValidateNodeUpdate(&test.node, &test.oldNode) | 		errs := ValidateNodeUpdate(&test.node, &test.oldNode, opts) | ||||||
| 		if test.valid && len(errs) > 0 { | 		if test.valid && len(errs) > 0 { | ||||||
| 			t.Errorf("%d: Unexpected error: %v", i, errs) | 			t.Errorf("%d: Unexpected error: %v", i, errs) | ||||||
| 			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) | 			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) | ||||||
| @@ -14779,6 +14909,9 @@ func makeNode(nodeName string, podCIDRs []string) core.Node { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func TestValidateNodeCIDRs(t *testing.T) { | func TestValidateNodeCIDRs(t *testing.T) { | ||||||
|  | 	opts := NodeValidationOptions{ | ||||||
|  | 		ValidateSingleHugePageResource: true, | ||||||
|  | 	} | ||||||
| 	testCases := []struct { | 	testCases := []struct { | ||||||
| 		expectError bool | 		expectError bool | ||||||
| 		node        core.Node | 		node        core.Node | ||||||
| @@ -14839,7 +14972,7 @@ func TestValidateNodeCIDRs(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	for _, testCase := range testCases { | 	for _, testCase := range testCases { | ||||||
| 		errs := ValidateNode(&testCase.node) | 		errs := ValidateNode(&testCase.node, opts) | ||||||
| 		if len(errs) == 0 && testCase.expectError { | 		if len(errs) == 0 && testCase.expectError { | ||||||
| 			t.Errorf("expected failure for %s, but there were none", testCase.node.Name) | 			t.Errorf("expected failure for %s, but there were none", testCase.node.Name) | ||||||
| 			return | 			return | ||||||
|   | |||||||
| @@ -44,6 +44,8 @@ go_test( | |||||||
|         "//pkg/apis/core:go_default_library", |         "//pkg/apis/core:go_default_library", | ||||||
|         "//pkg/apis/core/install:go_default_library", |         "//pkg/apis/core/install:go_default_library", | ||||||
|         "//pkg/features:go_default_library", |         "//pkg/features:go_default_library", | ||||||
|  |         "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", | ||||||
|  |         "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", | ||||||
|         "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", |         "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", | ||||||
|         "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", |         "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", | ||||||
|         "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", |         "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", | ||||||
|   | |||||||
| @@ -124,7 +124,12 @@ func nodeConfigSourceInUse(node *api.Node) bool { | |||||||
| // Validate validates a new node. | // Validate validates a new node. | ||||||
| func (nodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { | func (nodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { | ||||||
| 	node := obj.(*api.Node) | 	node := obj.(*api.Node) | ||||||
| 	return validation.ValidateNode(node) | 	opts := validation.NodeValidationOptions{ | ||||||
|  | 		// This ensures new nodes have no more than one hugepages resource | ||||||
|  | 		// TODO: set to false in 1.19; 1.18 servers tolerate multiple hugepages resources on update | ||||||
|  | 		ValidateSingleHugePageResource: true, | ||||||
|  | 	} | ||||||
|  | 	return validation.ValidateNode(node, opts) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Canonicalize normalizes the object after validation. | // Canonicalize normalizes the object after validation. | ||||||
| @@ -133,8 +138,14 @@ func (nodeStrategy) Canonicalize(obj runtime.Object) { | |||||||
|  |  | ||||||
| // ValidateUpdate is the default update validation for an end user. | // ValidateUpdate is the default update validation for an end user. | ||||||
| func (nodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { | func (nodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { | ||||||
| 	errorList := validation.ValidateNode(obj.(*api.Node)) | 	oldPassesSingleHugepagesValidation := len(validation.ValidateNodeSingleHugePageResources(old.(*api.Node))) == 0 | ||||||
| 	return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))...) | 	opts := validation.NodeValidationOptions{ | ||||||
|  | 		// This ensures the new node has no more than one hugepages resource, if the old node did as well. | ||||||
|  | 		// TODO: set to false in 1.19; 1.18 servers tolerate relaxed validation on update | ||||||
|  | 		ValidateSingleHugePageResource: oldPassesSingleHugepagesValidation, | ||||||
|  | 	} | ||||||
|  | 	errorList := validation.ValidateNode(obj.(*api.Node), opts) | ||||||
|  | 	return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node), opts)...) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (nodeStrategy) AllowUnconditionalUpdate() bool { | func (nodeStrategy) AllowUnconditionalUpdate() bool { | ||||||
| @@ -185,7 +196,13 @@ func nodeStatusConfigInUse(node *api.Node) bool { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (nodeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { | func (nodeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { | ||||||
| 	return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node)) | 	oldPassesSingleHugepagesValidation := len(validation.ValidateNodeSingleHugePageResources(old.(*api.Node))) == 0 | ||||||
|  | 	opts := validation.NodeValidationOptions{ | ||||||
|  | 		// This ensures the new node has no more than one hugepages resource, if the old node did as well. | ||||||
|  | 		// TODO: set to false in 1.19; 1.18 servers tolerate relaxed validation on update | ||||||
|  | 		ValidateSingleHugePageResource: oldPassesSingleHugepagesValidation, | ||||||
|  | 	} | ||||||
|  | 	return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node), opts) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Canonicalize normalizes the object after validation. | // Canonicalize normalizes the object after validation. | ||||||
|   | |||||||
| @@ -17,9 +17,12 @@ limitations under the License. | |||||||
| package node | package node | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"k8s.io/apimachinery/pkg/api/resource" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/fields" | 	"k8s.io/apimachinery/pkg/fields" | ||||||
| 	"k8s.io/apimachinery/pkg/labels" | 	"k8s.io/apimachinery/pkg/labels" | ||||||
| 	"k8s.io/apimachinery/pkg/util/diff" | 	"k8s.io/apimachinery/pkg/util/diff" | ||||||
| @@ -234,3 +237,107 @@ func TestDropFields(t *testing.T) { | |||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | func TestValidateUpdate(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		oldNode api.Node | ||||||
|  | 		node    api.Node | ||||||
|  | 		valid   bool | ||||||
|  | 	}{ | ||||||
|  | 		{api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "hugepage-change-values-from-0", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("0"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "hugepage-change-values-from-0", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, false}, | ||||||
|  | 		{api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "hugepage-change-values", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "hugepage-change-values", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, true}, | ||||||
|  | 	} | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		test.node.ObjectMeta.ResourceVersion = "1" | ||||||
|  | 		errs := (nodeStrategy{}).ValidateUpdate(context.Background(), &test.node, &test.oldNode) | ||||||
|  | 		if test.valid && len(errs) > 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected error: %v", i, errs) | ||||||
|  | 			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) | ||||||
|  | 		} | ||||||
|  | 		if !test.valid && len(errs) == 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected non-error", i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | func TestValidate(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		node  api.Node | ||||||
|  | 		valid bool | ||||||
|  | 	}{ | ||||||
|  | 		{api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "one-hugepage-size", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceCPU:                   resource.MustParse("100"), | ||||||
|  | 					api.ResourceMemory:                resource.MustParse("10000"), | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("0"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, true}, | ||||||
|  | 		{api.Node{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "multiple-hugepage-sizes", | ||||||
|  | 			}, | ||||||
|  | 			Status: api.NodeStatus{ | ||||||
|  | 				Capacity: api.ResourceList{ | ||||||
|  | 					api.ResourceCPU:                   resource.MustParse("100"), | ||||||
|  | 					api.ResourceMemory:                resource.MustParse("10000"), | ||||||
|  | 					api.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), | ||||||
|  | 					api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, false}, | ||||||
|  | 	} | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		test.node.ObjectMeta.ResourceVersion = "1" | ||||||
|  | 		errs := (nodeStrategy{}).Validate(context.Background(), &test.node) | ||||||
|  | 		if test.valid && len(errs) > 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected error: %v", i, errs) | ||||||
|  | 		} | ||||||
|  | 		if !test.valid && len(errs) == 0 { | ||||||
|  | 			t.Errorf("%d: Unexpected non-error", i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Odin Ugedal
					Odin Ugedal