Skip to content

Commit

Permalink
Merge pull request kubernetes#50859 from derekwaynecarr/hugepages-fea…
Browse files Browse the repository at this point in the history
…ture

Automatic merge from submit-queue

HugePages feature

**What this PR does / why we need it**:
Implements HugePages support per kubernetes/community#837

Feature track issue: kubernetes/enhancements#275

**Special notes for your reviewer**:
A follow-on PR is opened to add the EmptyDir support.

**Release note**:
```release-note
Alpha support for pre-allocated hugepages
```
  • Loading branch information
Kubernetes Submit Queue committed Sep 5, 2017
2 parents 775f5d2 + 38d5dee commit 2f543f3
Show file tree
Hide file tree
Showing 26 changed files with 682 additions and 46 deletions.
29 changes: 27 additions & 2 deletions pkg/api/helper/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ import (
"k8s.io/kubernetes/pkg/api"
)

// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name api.ResourceName) bool {
return strings.HasPrefix(string(name), api.ResourceHugePagesPrefix)
}

// HugePageResourceName returns a ResourceName with the canonical hugepage
// prefix prepended for the specified page size. The page size is converted
// to its canonical representation.
func HugePageResourceName(pageSize resource.Quantity) api.ResourceName {
return api.ResourceName(fmt.Sprintf("%s%s", api.ResourceHugePagesPrefix, pageSize.String()))
}

// HugePageSizeFromResourceName returns the page size for the specified huge page
// resource name. If the specified input is not a valid huge page resource name
// an error is returned.
func HugePageSizeFromResourceName(name api.ResourceName) (resource.Quantity, error) {
if !IsHugePageResourceName(name) {
return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name)
}
pageSize := strings.TrimPrefix(string(name), api.ResourceHugePagesPrefix)
return resource.ParseQuantity(pageSize)
}

// NonConvertibleFields iterates over the provided map and filters out all but
// any keys with the "non-convertible.kubernetes.io" prefix.
func NonConvertibleFields(annotations map[string]string) map[string]string {
Expand Down Expand Up @@ -113,7 +137,7 @@ var standardContainerResources = sets.NewString(
// IsStandardContainerResourceName returns true if the container can make a resource request
// for the specified resource
func IsStandardContainerResourceName(str string) bool {
return standardContainerResources.Has(str)
return standardContainerResources.Has(str) || IsHugePageResourceName(api.ResourceName(str))
}

// IsExtendedResourceName returns true if the resource name is not in the
Expand Down Expand Up @@ -153,6 +177,7 @@ var overcommitBlacklist = sets.NewString(string(api.ResourceNvidiaGPU))
// namespace and not blacklisted.
func IsOvercommitAllowed(name api.ResourceName) bool {
return IsDefaultNamespaceResource(name) &&
!IsHugePageResourceName(name) &&
!overcommitBlacklist.Has(string(name))
}

Expand Down Expand Up @@ -220,7 +245,7 @@ var standardResources = sets.NewString(

// IsStandardResourceName returns true if the resource is known to the system
func IsStandardResourceName(str string) bool {
return standardResources.Has(str)
return standardResources.Has(str) || IsHugePageResourceName(api.ResourceName(str))
}

var integerResources = sets.NewString(
Expand Down
137 changes: 136 additions & 1 deletion pkg/api/helper/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,28 @@ func TestIsStandardResource(t *testing.T) {
{"disk", false},
{"blah", false},
{"x.y.z", false},
{"hugepages-2Mi", true},
}
for i, tc := range testCases {
if IsStandardResourceName(tc.input) != tc.output {
t.Errorf("case[%d], expected: %t, got: %t", i, tc.output, !tc.output)
t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output)
}
}
}

func TestIsStandardContainerResource(t *testing.T) {
testCases := []struct {
input string
output bool
}{
{"cpu", true},
{"memory", true},
{"disk", false},
{"hugepages-2Mi", true},
}
for i, tc := range testCases {
if IsStandardContainerResourceName(tc.input) != tc.output {
t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output)
}
}
}
Expand Down Expand Up @@ -353,3 +371,120 @@ func TestGetNodeAffinityFromAnnotations(t *testing.T) {
}
}
}

func TestIsHugePageResourceName(t *testing.T) {
testCases := []struct {
name api.ResourceName
result bool
}{
{
name: api.ResourceName("hugepages-2Mi"),
result: true,
},
{
name: api.ResourceName("hugepages-1Gi"),
result: true,
},
{
name: api.ResourceName("cpu"),
result: false,
},
{
name: api.ResourceName("memory"),
result: false,
},
}
for _, testCase := range testCases {
if testCase.result != IsHugePageResourceName(testCase.name) {
t.Errorf("resource: %v expected result: %v", testCase.name, testCase.result)
}
}
}

func TestHugePageResourceName(t *testing.T) {
testCases := []struct {
pageSize resource.Quantity
name api.ResourceName
}{
{
pageSize: resource.MustParse("2Mi"),
name: api.ResourceName("hugepages-2Mi"),
},
{
pageSize: resource.MustParse("1Gi"),
name: api.ResourceName("hugepages-1Gi"),
},
{
// verify we do not regress our canonical representation
pageSize: *resource.NewQuantity(int64(2097152), resource.BinarySI),
name: api.ResourceName("hugepages-2Mi"),
},
}
for _, testCase := range testCases {
if result := HugePageResourceName(testCase.pageSize); result != testCase.name {
t.Errorf("pageSize: %v, expected: %v, but got: %v", testCase.pageSize.String(), testCase.name, result.String())
}
}
}

func TestHugePageSizeFromResourceName(t *testing.T) {
testCases := []struct {
name api.ResourceName
expectErr bool
pageSize resource.Quantity
}{
{
name: api.ResourceName("hugepages-2Mi"),
pageSize: resource.MustParse("2Mi"),
expectErr: false,
},
{
name: api.ResourceName("hugepages-1Gi"),
pageSize: resource.MustParse("1Gi"),
expectErr: false,
},
{
name: api.ResourceName("hugepages-bad"),
expectErr: true,
},
}
for _, testCase := range testCases {
value, err := HugePageSizeFromResourceName(testCase.name)
if testCase.expectErr && err == nil {
t.Errorf("Expected an error for %v", testCase.name)
} else if !testCase.expectErr && err != nil {
t.Errorf("Unexpected error for %v, got %v", testCase.name, err)
} else if testCase.pageSize.Value() != value.Value() {
t.Errorf("Unexpected pageSize for resource %v got %v", testCase.name, value.String())
}
}
}

func TestIsOvercommitAllowed(t *testing.T) {
testCases := []struct {
name api.ResourceName
allowed bool
}{
{
name: api.ResourceCPU,
allowed: true,
},
{
name: api.ResourceMemory,
allowed: true,
},
{
name: api.ResourceNvidiaGPU,
allowed: false,
},
{
name: HugePageResourceName(resource.MustParse("2Mi")),
allowed: false,
},
}
for _, testCase := range testCases {
if testCase.allowed != IsOvercommitAllowed(testCase.name) {
t.Errorf("Unexpected result for %v", testCase.name)
}
}
}
1 change: 1 addition & 0 deletions pkg/api/helper/qos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ go_library(
srcs = ["qos.go"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/helper:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
Expand Down
13 changes: 8 additions & 5 deletions pkg/api/helper/qos/qos.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/helper"
)

// supportedComputeResources is the list of compute resources for with QoS is supported.
var supportedQoSComputeResources = sets.NewString(string(api.ResourceCPU), string(api.ResourceMemory))
func isSupportedQoSComputeResource(name api.ResourceName) bool {
supportedQoSComputeResources := sets.NewString(string(api.ResourceCPU), string(api.ResourceMemory))
return supportedQoSComputeResources.Has(string(name)) || helper.IsHugePageResourceName(name)
}

// GetPodQOS returns the QoS class of a pod.
// A pod is besteffort if none of its containers have specified any requests or limits.
Expand All @@ -39,7 +42,7 @@ func GetPodQOS(pod *api.Pod) api.PodQOSClass {
for _, container := range pod.Spec.Containers {
// process requests
for name, quantity := range container.Resources.Requests {
if !supportedQoSComputeResources.Has(string(name)) {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
Expand All @@ -55,7 +58,7 @@ func GetPodQOS(pod *api.Pod) api.PodQOSClass {
// process limits
qosLimitsFound := sets.NewString()
for name, quantity := range container.Resources.Limits {
if !supportedQoSComputeResources.Has(string(name)) {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
Expand All @@ -70,7 +73,7 @@ func GetPodQOS(pod *api.Pod) api.PodQOSClass {
}
}

if len(qosLimitsFound) != len(supportedQoSComputeResources) {
if !qosLimitsFound.HasAll(string(api.ResourceMemory), string(api.ResourceCPU)) {
isGuaranteed = false
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,8 @@ const (
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
// Default namespace prefix.
ResourceDefaultNamespacePrefix = "kubernetes.io/"
// Name prefix for huge page resources (alpha).
ResourceHugePagesPrefix = "hugepages-"
)

// ResourceList is a set of (resource name, quantity) pairs.
Expand Down
1 change: 1 addition & 0 deletions pkg/api/v1/helper/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
deps = [
"//pkg/api/helper:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/selection:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
Expand Down
26 changes: 26 additions & 0 deletions pkg/api/v1/helper/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/sets"
Expand All @@ -41,6 +42,31 @@ func IsExtendedResourceName(name v1.ResourceName) bool {
func IsDefaultNamespaceResource(name v1.ResourceName) bool {
return !strings.Contains(string(name), "/") ||
strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)

}

// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name v1.ResourceName) bool {
return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
}

// HugePageResourceName returns a ResourceName with the canonical hugepage
// prefix prepended for the specified page size. The page size is converted
// to its canonical representation.
func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName {
return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String()))
}

// HugePageSizeFromResourceName returns the page size for the specified huge page
// resource name. If the specified input is not a valid huge page resource name
// an error is returned.
func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) {
if !IsHugePageResourceName(name) {
return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name)
}
pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix)
return resource.ParseQuantity(pageSize)
}

// IsOpaqueIntResourceName returns true if the resource name has the opaque
Expand Down
1 change: 1 addition & 0 deletions pkg/api/v1/helper/qos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
name = "go_default_library",
srcs = ["qos.go"],
deps = [
"//pkg/api/v1/helper:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
Expand Down
12 changes: 8 additions & 4 deletions pkg/api/v1/helper/qos/qos.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
)

// QOSList is a set of (resource name, QoS class) pairs.
type QOSList map[v1.ResourceName]v1.PodQOSClass

var supportedQoSComputeResources = sets.NewString(string(v1.ResourceCPU), string(v1.ResourceMemory))
func isSupportedQoSComputeResource(name v1.ResourceName) bool {
supportedQoSComputeResources := sets.NewString(string(v1.ResourceCPU), string(v1.ResourceMemory))
return supportedQoSComputeResources.Has(string(name)) || v1helper.IsHugePageResourceName(name)
}

// GetPodQOS returns the QoS class of a pod.
// A pod is besteffort if none of its containers have specified any requests or limits.
Expand All @@ -39,7 +43,7 @@ func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
for _, container := range pod.Spec.Containers {
// process requests
for name, quantity := range container.Resources.Requests {
if !supportedQoSComputeResources.Has(string(name)) {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
Expand All @@ -55,7 +59,7 @@ func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
// process limits
qosLimitsFound := sets.NewString()
for name, quantity := range container.Resources.Limits {
if !supportedQoSComputeResources.Has(string(name)) {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
Expand All @@ -70,7 +74,7 @@ func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
}
}

if len(qosLimitsFound) != len(supportedQoSComputeResources) {
if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
isGuaranteed = false
}
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/api/v1/helper/qos/qos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ func TestGetPodQOS(t *testing.T) {
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-hugepages", []v1.Container{
newContainer("burstable", addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0")), addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0"))),
}),
expected: v1.PodQOSBurstable,
},
}
for id, testCase := range testCases {
if actual := GetPodQOS(testCase.pod); testCase.expected != actual {
Expand All @@ -141,7 +147,7 @@ func TestGetPodQOS(t *testing.T) {
k8sv1.Convert_v1_Pod_To_api_Pod(testCase.pod, &pod, nil)

if actual := qos.GetPodQOS(&pod); api.PodQOSClass(testCase.expected) != actual {
t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
t.Errorf("[%d]: conversion invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
}
}
}
Expand Down
Loading

0 comments on commit 2f543f3

Please sign in to comment.