mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 09:49:50 +00:00
Adding ResourceRequirementSpec to v1beta1, v1beta2, and v1beta3 APIs. The old resource
quantities 'CPU' and 'Memory' will be preserved until support for v1beta1 and v1beta2 APIs are dropped. Improved resource validation in the process.
This commit is contained in:
parent
aa3b45d17b
commit
5e36f63f8b
42
pkg/api/resource_helpers.go
Normal file
42
pkg/api/resource_helpers.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns string version of ResourceName.
|
||||||
|
func (self ResourceName) String() string {
|
||||||
|
return string(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CPU limit if specified.
|
||||||
|
func (self *ResourceList) Cpu() *resource.Quantity {
|
||||||
|
if val, ok := (*self)[ResourceCPU]; ok {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
return &resource.Quantity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Memory limit if specified.
|
||||||
|
func (self *ResourceList) Memory() *resource.Quantity {
|
||||||
|
if val, ok := (*self)[ResourceMemory]; ok {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
return &resource.Quantity{}
|
||||||
|
}
|
53
pkg/api/resource_helpers_test.go
Normal file
53
pkg/api/resource_helpers_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResourceHelpers(t *testing.T) {
|
||||||
|
cpuLimit := resource.MustParse("10")
|
||||||
|
memoryLimit := resource.MustParse("10G")
|
||||||
|
resourceSpec := ResourceRequirementSpec{
|
||||||
|
Limits: ResourceList{
|
||||||
|
"cpu": cpuLimit,
|
||||||
|
"memory": memoryLimit,
|
||||||
|
"kube.io/storage": memoryLimit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if res := resourceSpec.Limits.Cpu(); *res != cpuLimit {
|
||||||
|
t.Errorf("expected cpulimit %d, got %d", cpuLimit, res)
|
||||||
|
}
|
||||||
|
if res := resourceSpec.Limits.Memory(); *res != memoryLimit {
|
||||||
|
t.Errorf("expected memorylimit %d, got %d", memoryLimit, res)
|
||||||
|
}
|
||||||
|
resourceSpec = ResourceRequirementSpec{
|
||||||
|
Limits: ResourceList{
|
||||||
|
"memory": memoryLimit,
|
||||||
|
"kube.io/storage": memoryLimit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if res := resourceSpec.Limits.Cpu(); res.Value() != 0 {
|
||||||
|
t.Errorf("expected cpulimit %d, got %d", 0, res)
|
||||||
|
}
|
||||||
|
if res := resourceSpec.Limits.Memory(); *res != memoryLimit {
|
||||||
|
t.Errorf("expected memorylimit %d, got %d", memoryLimit, res)
|
||||||
|
}
|
||||||
|
}
|
@ -304,6 +304,12 @@ type Capabilities struct {
|
|||||||
Drop []CapabilityType `json:"drop,omitempty"`
|
Drop []CapabilityType `json:"drop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceRequirementSpec describes the compute resource requirements.
|
||||||
|
type ResourceRequirementSpec struct {
|
||||||
|
// Limits describes the maximum amount of compute resources required.
|
||||||
|
Limits ResourceList `json:"limits,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Container represents a single container that is expected to be run on the host.
|
// Container represents a single container that is expected to be run on the host.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
// Required: This must be a DNS_LABEL. Each container in a pod must
|
// Required: This must be a DNS_LABEL. Each container in a pod must
|
||||||
@ -317,13 +323,11 @@ type Container struct {
|
|||||||
WorkingDir string `json:"workingDir,omitempty"`
|
WorkingDir string `json:"workingDir,omitempty"`
|
||||||
Ports []Port `json:"ports,omitempty"`
|
Ports []Port `json:"ports,omitempty"`
|
||||||
Env []EnvVar `json:"env,omitempty"`
|
Env []EnvVar `json:"env,omitempty"`
|
||||||
// Optional: Defaults to unlimited.
|
// Compute resource requirements.
|
||||||
Memory resource.Quantity `json:"memory,omitempty"`
|
Resources ResourceRequirementSpec `json:"resources,omitempty"`
|
||||||
// Optional: Defaults to unlimited.
|
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||||
CPU resource.Quantity `json:"cpu,omitempty"`
|
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
|
||||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
|
||||||
// Optional: Defaults to /dev/termination-log
|
// Optional: Defaults to /dev/termination-log
|
||||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||||
// Optional: Default to false.
|
// Optional: Default to false.
|
||||||
@ -775,8 +779,6 @@ type NodeResources struct {
|
|||||||
type ResourceName string
|
type ResourceName string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The default compute resource namespace for all standard resource types.
|
|
||||||
DefaultResourceNamespace = "kubernetes.io"
|
|
||||||
// CPU, in cores. (500m = .5 cores)
|
// CPU, in cores. (500m = .5 cores)
|
||||||
ResourceCPU ResourceName = "cpu"
|
ResourceCPU ResourceName = "cpu"
|
||||||
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
||||||
|
@ -444,7 +444,140 @@ func init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
// Converts internal Container to v1beta1.Container.
|
||||||
|
// Fields 'CPU' and 'Memory' are not present in the internal Container object.
|
||||||
|
// Hence the need for a custom conversion function.
|
||||||
|
func(in *newer.Container, out *Container, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.Name, &out.Name, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Ports, &out.Ports, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Env, &out.Env, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(in.Resources.Limits.Cpu(), &out.CPU, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(in.Resources.Limits.Memory(), &out.Memory, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.VolumeMounts, &out.VolumeMounts, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.LivenessProbe, &out.LivenessProbe, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Lifecycle, &out.Lifecycle, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Internal API does not support CPU to be specified via an explicit field.
|
||||||
|
// Hence it must be stored in Container.Resources.
|
||||||
|
func(in *int, out *newer.ResourceList, s conversion.Scope) error {
|
||||||
|
if *in <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
quantity := resource.Quantity{}
|
||||||
|
if err := s.Convert(in, &quantity, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
(*out)[newer.ResourceCPU] = quantity
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Internal API does not support Memory to be specified via an explicit field.
|
||||||
|
// Hence it must be stored in Container.Resources.
|
||||||
|
func(in *int64, out *newer.ResourceList, s conversion.Scope) error {
|
||||||
|
if *in <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
quantity := resource.Quantity{}
|
||||||
|
if err := s.Convert(in, &quantity, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
(*out)[newer.ResourceMemory] = quantity
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Converts v1beta1.Container to internal Container.
|
||||||
|
// Fields 'CPU' and 'Memory' are not present in the internal Container object.
|
||||||
|
// Hence the need for a custom conversion function.
|
||||||
|
func(in *Container, out *newer.Container, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.Name, &out.Name, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Ports, &out.Ports, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Env, &out.Env, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.CPU, &out.Resources.Limits, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Memory, &out.Resources.Limits, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.VolumeMounts, &out.VolumeMounts, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.LivenessProbe, &out.LivenessProbe, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Lifecycle, &out.Lifecycle, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
func(in *newer.PodSpec, out *ContainerManifest, s conversion.Scope) error {
|
func(in *newer.PodSpec, out *ContainerManifest, s conversion.Scope) error {
|
||||||
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -22,7 +22,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Convert = newer.Scheme.Convert
|
var Convert = newer.Scheme.Convert
|
||||||
@ -316,3 +318,67 @@ func TestPullPolicyConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getResourceRequirements(cpu, memory resource.Quantity) current.ResourceRequirementSpec {
|
||||||
|
res := current.ResourceRequirementSpec{}
|
||||||
|
res.Limits = current.ResourceList{}
|
||||||
|
if cpu.Value() > 0 {
|
||||||
|
res.Limits[current.ResourceCPU] = util.NewIntOrStringFromInt(int(cpu.Value()))
|
||||||
|
}
|
||||||
|
if memory.Value() > 0 {
|
||||||
|
res.Limits[current.ResourceMemory] = util.NewIntOrStringFromInt(int(memory.Value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerConversion(t *testing.T) {
|
||||||
|
cpuLimit := resource.MustParse("10")
|
||||||
|
memoryLimit := resource.MustParse("10M")
|
||||||
|
null := resource.Quantity{}
|
||||||
|
testCases := []current.Container{
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Resources: getResourceRequirements(cpuLimit, memoryLimit),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Resources: getResourceRequirements(null, memoryLimit),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
Resources: getResourceRequirements(cpuLimit, null),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
Resources: getResourceRequirements(cpuLimit, resource.MustParse("100M")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Resources: getResourceRequirements(resource.MustParse("500"), memoryLimit),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
got := newer.Container{}
|
||||||
|
if err := Convert(&tc, &got); err != nil {
|
||||||
|
t.Errorf("[Case: %d] Unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cpu := got.Resources.Limits.Cpu(); cpu.Value() != cpuLimit.Value() {
|
||||||
|
t.Errorf("[Case: %d] Expected cpu: %v, got: %v", i, cpuLimit, *cpu)
|
||||||
|
}
|
||||||
|
if memory := got.Resources.Limits.Memory(); memory.Value() != memoryLimit.Value() {
|
||||||
|
t.Errorf("[Case: %d] Expected memory: %v, got: %v", i, memoryLimit, *memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -252,6 +252,11 @@ type Capabilities struct {
|
|||||||
Drop []CapabilityType `json:"drop,omitempty" description:"droped capabilities"`
|
Drop []CapabilityType `json:"drop,omitempty" description:"droped capabilities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceRequirementSpec struct {
|
||||||
|
// Limits describes the maximum amount of compute resources required.
|
||||||
|
Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"`
|
||||||
|
}
|
||||||
|
|
||||||
// Container represents a single container that is expected to be run on the host.
|
// Container represents a single container that is expected to be run on the host.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
// Required: This must be a DNS_LABEL. Each container in a pod must
|
// Required: This must be a DNS_LABEL. Each container in a pod must
|
||||||
@ -262,13 +267,14 @@ type Container struct {
|
|||||||
// Optional: Defaults to whatever is defined in the image.
|
// Optional: Defaults to whatever is defined in the image.
|
||||||
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image"`
|
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image"`
|
||||||
// Optional: Defaults to Docker's default.
|
// Optional: Defaults to Docker's default.
|
||||||
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default"`
|
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default"`
|
||||||
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"`
|
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"`
|
||||||
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
||||||
|
Resources ResourceRequirementSpec `json:"resources,omitempty" description:"Compute Resources required by this container"`
|
||||||
// Optional: Defaults to unlimited.
|
// Optional: Defaults to unlimited.
|
||||||
Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"`
|
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"`
|
||||||
// Optional: Defaults to unlimited.
|
// Optional: Defaults to unlimited.
|
||||||
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"`
|
Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"`
|
||||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
|
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
|
||||||
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty" description:"periodic probe of container liveness; container will be restarted if the probe fails"`
|
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty" description:"periodic probe of container liveness; container will be restarted if the probe fails"`
|
||||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events"`
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events"`
|
||||||
|
@ -295,7 +295,143 @@ func init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
// Converts internal Container to v1beta1.Container.
|
||||||
|
// Fields 'CPU' and 'Memory' are not present in the internal Container object.
|
||||||
|
// Hence the need for a custom conversion function.
|
||||||
|
func(in *newer.Container, out *Container, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.Name, &out.Name, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Convert(&in.Ports, &out.Ports, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Env, &out.Env, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(in.Resources.Limits.Cpu(), &out.CPU, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(in.Resources.Limits.Memory(), &out.Memory, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.VolumeMounts, &out.VolumeMounts, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.LivenessProbe, &out.LivenessProbe, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Lifecycle, &out.Lifecycle, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Internal API does not support CPU to be specified via an explicit field.
|
||||||
|
// Hence it must be stored in Container.Resources.
|
||||||
|
func(in *int, out *newer.ResourceList, s conversion.Scope) error {
|
||||||
|
if *in == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
quantity := resource.Quantity{}
|
||||||
|
if err := s.Convert(in, &quantity, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
(*out)[newer.ResourceCPU] = quantity
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Internal API does not support Memory to be specified via an explicit field.
|
||||||
|
// Hence it must be stored in Container.Resources.
|
||||||
|
func(in *int64, out *newer.ResourceList, s conversion.Scope) error {
|
||||||
|
if *in == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
quantity := resource.Quantity{}
|
||||||
|
if err := s.Convert(in, &quantity, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
(*out)[newer.ResourceMemory] = quantity
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Converts v1beta1.Container to internal newer.Container.
|
||||||
|
// Fields 'CPU' and 'Memory' are not present in the internal newer.Container object.
|
||||||
|
// Hence the need for a custom conversion function.
|
||||||
|
func(in *Container, out *newer.Container, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.Name, &out.Name, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Ports, &out.Ports, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Env, &out.Env, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.CPU, &out.Resources.Limits, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Memory, &out.Resources.Limits, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.VolumeMounts, &out.VolumeMounts, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.LivenessProbe, &out.LivenessProbe, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Lifecycle, &out.Lifecycle, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
func(in *newer.PodSpec, out *ContainerManifest, s conversion.Scope) error {
|
func(in *newer.PodSpec, out *ContainerManifest, s conversion.Scope) error {
|
||||||
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -21,7 +21,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceEmptySelector(t *testing.T) {
|
func TestServiceEmptySelector(t *testing.T) {
|
||||||
@ -146,3 +148,67 @@ func TestPullPolicyConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getResourceRequirements(cpu, memory resource.Quantity) current.ResourceRequirementSpec {
|
||||||
|
res := current.ResourceRequirementSpec{}
|
||||||
|
res.Limits = current.ResourceList{}
|
||||||
|
if cpu.Value() > 0 {
|
||||||
|
res.Limits[current.ResourceCPU] = util.NewIntOrStringFromInt(int(cpu.Value()))
|
||||||
|
}
|
||||||
|
if memory.Value() > 0 {
|
||||||
|
res.Limits[current.ResourceMemory] = util.NewIntOrStringFromInt(int(memory.Value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerConversion(t *testing.T) {
|
||||||
|
cpuLimit := resource.MustParse("10")
|
||||||
|
memoryLimit := resource.MustParse("10M")
|
||||||
|
null := resource.Quantity{}
|
||||||
|
testCases := []current.Container{
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Resources: getResourceRequirements(cpuLimit, memoryLimit),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Resources: getResourceRequirements(null, memoryLimit),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
Resources: getResourceRequirements(cpuLimit, null),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
Memory: memoryLimit.Value(),
|
||||||
|
Resources: getResourceRequirements(cpuLimit, resource.MustParse("100M")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "container",
|
||||||
|
CPU: int(cpuLimit.MilliValue()),
|
||||||
|
Resources: getResourceRequirements(resource.MustParse("500"), memoryLimit),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
got := newer.Container{}
|
||||||
|
if err := newer.Scheme.Convert(&tc, &got); err != nil {
|
||||||
|
t.Errorf("[Case: %d] Unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cpu := got.Resources.Limits.Cpu(); cpu.Value() != cpuLimit.Value() {
|
||||||
|
t.Errorf("[Case: %d] Expected cpu: %v, got: %v", i, cpuLimit, *cpu)
|
||||||
|
}
|
||||||
|
if memory := got.Resources.Limits.Memory(); memory.Value() != memoryLimit.Value() {
|
||||||
|
t.Errorf("[Case: %d] Expected memory: %v, got: %v", i, memoryLimit, *memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -216,6 +216,11 @@ type Capabilities struct {
|
|||||||
Drop []CapabilityType `json:"drop,omitempty" description:"droped capabilities"`
|
Drop []CapabilityType `json:"drop,omitempty" description:"droped capabilities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceRequirementSpec struct {
|
||||||
|
// Limits describes the maximum amount of compute resources required.
|
||||||
|
Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"`
|
||||||
|
}
|
||||||
|
|
||||||
// Container represents a single container that is expected to be run on the host.
|
// Container represents a single container that is expected to be run on the host.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
// Required: This must be a DNS_LABEL. Each container in a pod must
|
// Required: This must be a DNS_LABEL. Each container in a pod must
|
||||||
@ -226,13 +231,14 @@ type Container struct {
|
|||||||
// Optional: Defaults to whatever is defined in the image.
|
// Optional: Defaults to whatever is defined in the image.
|
||||||
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image"`
|
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image"`
|
||||||
// Optional: Defaults to Docker's default.
|
// Optional: Defaults to Docker's default.
|
||||||
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default"`
|
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default"`
|
||||||
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"`
|
Ports []Port `json:"ports,omitempty" description:"list of ports to expose from the container"`
|
||||||
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
||||||
|
Resources ResourceRequirementSpec `json:"resources,omitempty" description:"Compute Resources required by this container"`
|
||||||
// Optional: Defaults to unlimited.
|
// Optional: Defaults to unlimited.
|
||||||
Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"`
|
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"`
|
||||||
// Optional: Defaults to unlimited.
|
// Optional: Defaults to unlimited.
|
||||||
CPU int `json:"cpu,omitempty" description:"CPU share in thousandths of a core"`
|
Memory int64 `json:"memory,omitempty" description:"memory limit in bytes; defaults to unlimited"`
|
||||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
|
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" description:"pod volumes to mount into the container's filesystem"`
|
||||||
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty" description:"periodic probe of container liveness; container will be restarted if the probe fails"`
|
LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty" description:"periodic probe of container liveness; container will be restarted if the probe fails"`
|
||||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events"`
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events"`
|
||||||
|
@ -322,6 +322,12 @@ type Capabilities struct {
|
|||||||
Drop []CapabilityType `json:"drop,omitempty"`
|
Drop []CapabilityType `json:"drop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceRequirementSpec describes the compute resource requirements.
|
||||||
|
type ResourceRequirementSpec struct {
|
||||||
|
// Limits describes the maximum amount of compute resources required.
|
||||||
|
Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"`
|
||||||
|
}
|
||||||
|
|
||||||
// Container represents a single container that is expected to be run on the host.
|
// Container represents a single container that is expected to be run on the host.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
// Required: This must be a DNS_LABEL. Each container in a pod must
|
// Required: This must be a DNS_LABEL. Each container in a pod must
|
||||||
@ -332,16 +338,13 @@ type Container struct {
|
|||||||
// Optional: Defaults to whatever is defined in the image.
|
// Optional: Defaults to whatever is defined in the image.
|
||||||
Command []string `json:"command,omitempty"`
|
Command []string `json:"command,omitempty"`
|
||||||
// Optional: Defaults to Docker's default.
|
// Optional: Defaults to Docker's default.
|
||||||
WorkingDir string `json:"workingDir,omitempty"`
|
WorkingDir string `json:"workingDir,omitempty"`
|
||||||
Ports []Port `json:"ports,omitempty"`
|
Ports []Port `json:"ports,omitempty"`
|
||||||
Env []EnvVar `json:"env,omitempty"`
|
Env []EnvVar `json:"env,omitempty"`
|
||||||
// Optional: Defaults to unlimited. Units: bytes.
|
Resources ResourceRequirementSpec `json:"resources,omitempty" description:"Compute Resources required by this container"`
|
||||||
Memory resource.Quantity `json:"memory,omitempty"`
|
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||||
// Optional: Defaults to unlimited. Units: Cores. (500m == 1/2 core)
|
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||||
CPU resource.Quantity `json:"cpu,omitempty"`
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
|
||||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
|
||||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
|
||||||
// Optional: Defaults to /dev/termination-log
|
// Optional: Defaults to /dev/termination-log
|
||||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||||
// Optional: Default to false.
|
// Optional: Default to false.
|
||||||
@ -796,8 +799,6 @@ type NodeCondition struct {
|
|||||||
type ResourceName string
|
type ResourceName string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The default compute resource namespace for all standard resource types.
|
|
||||||
DefaultResourceNamespace = "kubernetes.io"
|
|
||||||
// CPU, in cores. (500m = .5 cores)
|
// CPU, in cores. (500m = .5 cores)
|
||||||
ResourceCPU ResourceName = "cpu"
|
ResourceCPU ResourceName = "cpu"
|
||||||
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
errs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
errs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
@ -395,6 +396,7 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs
|
|||||||
cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...)
|
cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...)
|
||||||
cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
|
cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
|
||||||
cErrs = append(cErrs, validatePullPolicyWithDefault(ctr).Prefix("pullPolicy")...)
|
cErrs = append(cErrs, validatePullPolicyWithDefault(ctr).Prefix("pullPolicy")...)
|
||||||
|
cErrs = append(cErrs, validateResourceRequirements(ctr).Prefix("resources")...)
|
||||||
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
||||||
}
|
}
|
||||||
// Check for colliding ports across all containers.
|
// Check for colliding ports across all containers.
|
||||||
@ -696,28 +698,17 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typename is a generic representation for all compute resource typenames.
|
// Validate compute resource typename.
|
||||||
// Refer to docs/resources.md for more details.
|
// Refer to docs/resources.md for more details.
|
||||||
func ValidateResourceName(str string) errs.ValidationErrorList {
|
func validateResourceName(str string) errs.ValidationErrorList {
|
||||||
if !util.IsQualifiedName(str) {
|
if !util.IsQualifiedName(str) {
|
||||||
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename format %q", str)}
|
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename format %q", str)}
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(str, "/")
|
if len(strings.Split(str, "/")) == 1 {
|
||||||
switch len(parts) {
|
if !api.IsStandardResourceName(str) {
|
||||||
case 1:
|
|
||||||
if !api.IsStandardResourceName(parts[0]) {
|
|
||||||
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename. %q is neither a standard resource type nor is fully qualified", str)}
|
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename. %q is neither a standard resource type nor is fully qualified", str)}
|
||||||
}
|
}
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
if parts[0] == api.DefaultResourceNamespace {
|
|
||||||
if !api.IsStandardResourceName(parts[1]) {
|
|
||||||
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename. %q contains a compute resource type not supported", str)}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs.ValidationErrorList{}
|
return errs.ValidationErrorList{}
|
||||||
@ -740,15 +731,37 @@ func ValidateLimitRange(limitRange *api.LimitRange) errs.ValidationErrorList {
|
|||||||
for i := range limitRange.Spec.Limits {
|
for i := range limitRange.Spec.Limits {
|
||||||
limit := limitRange.Spec.Limits[i]
|
limit := limitRange.Spec.Limits[i]
|
||||||
for k := range limit.Max {
|
for k := range limit.Max {
|
||||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||||
}
|
}
|
||||||
for k := range limit.Min {
|
for k := range limit.Min {
|
||||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateBasicResource(quantity resource.Quantity) errs.ValidationErrorList {
|
||||||
|
if quantity.Value() < 0 {
|
||||||
|
return errs.ValidationErrorList{fmt.Errorf("%v is not a valid resource quantity", quantity.Value())}
|
||||||
|
}
|
||||||
|
return errs.ValidationErrorList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates resource requirement spec.
|
||||||
|
func validateResourceRequirements(container *api.Container) errs.ValidationErrorList {
|
||||||
|
allErrs := errs.ValidationErrorList{}
|
||||||
|
for resourceName, quantity := range container.Resources.Limits {
|
||||||
|
// Validate resource name.
|
||||||
|
errs := validateResourceName(resourceName.String())
|
||||||
|
if api.IsStandardResourceName(resourceName.String()) {
|
||||||
|
errs = append(errs, validateBasicResource(quantity).Prefix(fmt.Sprintf("Resource %s: ", resourceName))...)
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateResourceQuota tests if required fields in the ResourceQuota are set.
|
// ValidateResourceQuota tests if required fields in the ResourceQuota are set.
|
||||||
func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErrorList {
|
func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErrorList {
|
||||||
allErrs := errs.ValidationErrorList{}
|
allErrs := errs.ValidationErrorList{}
|
||||||
@ -763,13 +776,13 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
|||||||
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", resourceQuota.Namespace, ""))
|
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", resourceQuota.Namespace, ""))
|
||||||
}
|
}
|
||||||
for k := range resourceQuota.Spec.Hard {
|
for k := range resourceQuota.Spec.Hard {
|
||||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||||
}
|
}
|
||||||
for k := range resourceQuota.Status.Hard {
|
for k := range resourceQuota.Status.Hard {
|
||||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||||
}
|
}
|
||||||
for k := range resourceQuota.Status.Used {
|
for k := range resourceQuota.Status.Used {
|
||||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -268,6 +268,13 @@ func TestValidatePullPolicy(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getResourceLimits(cpu, memory string) api.ResourceList {
|
||||||
|
res := api.ResourceList{}
|
||||||
|
res[api.ResourceCPU] = resource.MustParse(cpu)
|
||||||
|
res[api.ResourceMemory] = resource.MustParse(memory)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateContainers(t *testing.T) {
|
func TestValidateContainers(t *testing.T) {
|
||||||
volumes := util.StringSet{}
|
volumes := util.StringSet{}
|
||||||
capabilities.SetForTests(capabilities.Capabilities{
|
capabilities.SetForTests(capabilities.Capabilities{
|
||||||
@ -287,6 +294,17 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "resources-test",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{Name: "abc-1234", Image: "image", Privileged: true},
|
{Name: "abc-1234", Image: "image", Privileged: true},
|
||||||
}
|
}
|
||||||
if errs := validateContainers(successCase, volumes); len(errs) != 0 {
|
if errs := validateContainers(successCase, volumes); len(errs) != 0 {
|
||||||
@ -349,6 +367,35 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
"privilege disabled": {
|
"privilege disabled": {
|
||||||
{Name: "abc", Image: "image", Privileged: true},
|
{Name: "abc", Image: "image", Privileged: true},
|
||||||
},
|
},
|
||||||
|
"invalid compute resource": {
|
||||||
|
{
|
||||||
|
Name: "abc-123",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
"disk": resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Resource CPU invalid": {
|
||||||
|
{
|
||||||
|
Name: "abc-123",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: getResourceLimits("-10", "0"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Resource Memory invalid": {
|
||||||
|
{
|
||||||
|
Name: "abc-123",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: getResourceLimits("0", "-10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
if errs := validateContainers(v, volumes); len(errs) == 0 {
|
if errs := validateContainers(v, volumes); len(errs) == 0 {
|
||||||
@ -422,8 +469,12 @@ func TestValidateManifest(t *testing.T) {
|
|||||||
Image: "image",
|
Image: "image",
|
||||||
Command: []string{"foo", "bar"},
|
Command: []string{"foo", "bar"},
|
||||||
WorkingDir: "/tmp",
|
WorkingDir: "/tmp",
|
||||||
Memory: resource.MustParse("1"),
|
Resources: api.ResourceRequirementSpec{
|
||||||
CPU: resource.MustParse("1"),
|
Limits: api.ResourceList{
|
||||||
|
"cpu": resource.MustParse("1"),
|
||||||
|
"memory": resource.MustParse("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
Ports: []api.Port{
|
Ports: []api.Port{
|
||||||
{Name: "p1", ContainerPort: 80, HostPort: 8080},
|
{Name: "p1", ContainerPort: 80, HostPort: 8080},
|
||||||
{Name: "p2", ContainerPort: 81},
|
{Name: "p2", ContainerPort: 81},
|
||||||
@ -711,7 +762,9 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "foo:V1",
|
Image: "foo:V1",
|
||||||
CPU: resource.MustParse("100m"),
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: getResourceLimits("100m", "0"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -722,7 +775,9 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "foo:V2",
|
Image: "foo:V2",
|
||||||
CPU: resource.MustParse("1000m"),
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: getResourceLimits("1000m", "0"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1675,8 +1730,6 @@ func TestValidateResourceNames(t *testing.T) {
|
|||||||
{"", false},
|
{"", false},
|
||||||
{".", false},
|
{".", false},
|
||||||
{"..", false},
|
{"..", false},
|
||||||
{"kubernetes.io/cpu", true},
|
|
||||||
{"kubernetes.io/disk", false},
|
|
||||||
{"my.favorite.app.co/12345", true},
|
{"my.favorite.app.co/12345", true},
|
||||||
{"my.favorite.app.co/_12345", false},
|
{"my.favorite.app.co/_12345", false},
|
||||||
{"my.favorite.app.co/12345_", false},
|
{"my.favorite.app.co/12345_", false},
|
||||||
@ -1687,7 +1740,7 @@ func TestValidateResourceNames(t *testing.T) {
|
|||||||
{"kubernetes.io/will/not/work/", false},
|
{"kubernetes.io/will/not/work/", false},
|
||||||
}
|
}
|
||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
err := ValidateResourceName(item.input)
|
err := validateResourceName(item.input)
|
||||||
if len(err) != 0 && item.success {
|
if len(err) != 0 && item.success {
|
||||||
t.Errorf("expected no failure for input %q", item.input)
|
t.Errorf("expected no failure for input %q", item.input)
|
||||||
} else if len(err) == 0 && !item.success {
|
} else if len(err) == 0 && !item.success {
|
||||||
|
@ -649,8 +649,8 @@ func (kl *Kubelet) runContainer(pod *api.BoundPod, container *api.Container, pod
|
|||||||
ExposedPorts: exposedPorts,
|
ExposedPorts: exposedPorts,
|
||||||
Hostname: pod.Name,
|
Hostname: pod.Name,
|
||||||
Image: container.Image,
|
Image: container.Image,
|
||||||
Memory: container.Memory.Value(),
|
Memory: container.Resources.Limits.Memory().Value(),
|
||||||
CPUShares: milliCPUToShares(container.CPU.MilliValue()),
|
CPUShares: milliCPUToShares(container.Resources.Limits.Cpu().MilliValue()),
|
||||||
WorkingDir: container.WorkingDir,
|
WorkingDir: container.WorkingDir,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ func (rm *ResourceQuotaManager) syncResourceQuota(quota api.ResourceQuota) (err
|
|||||||
func PodCPU(pod *api.Pod) *resource.Quantity {
|
func PodCPU(pod *api.Pod) *resource.Quantity {
|
||||||
val := int64(0)
|
val := int64(0)
|
||||||
for j := range pod.Spec.Containers {
|
for j := range pod.Spec.Containers {
|
||||||
val = val + pod.Spec.Containers[j].CPU.MilliValue()
|
val = val + pod.Spec.Containers[j].Resources.Limits.Cpu().MilliValue()
|
||||||
}
|
}
|
||||||
return resource.NewMilliQuantity(int64(val), resource.DecimalSI)
|
return resource.NewMilliQuantity(int64(val), resource.DecimalSI)
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ func PodCPU(pod *api.Pod) *resource.Quantity {
|
|||||||
func PodMemory(pod *api.Pod) *resource.Quantity {
|
func PodMemory(pod *api.Pod) *resource.Quantity {
|
||||||
val := int64(0)
|
val := int64(0)
|
||||||
for j := range pod.Spec.Containers {
|
for j := range pod.Spec.Containers {
|
||||||
val = val + pod.Spec.Containers[j].Memory.Value()
|
val = val + pod.Spec.Containers[j].Resources.Limits.Memory().Value()
|
||||||
}
|
}
|
||||||
return resource.NewQuantity(int64(val), resource.DecimalSI)
|
return resource.NewQuantity(int64(val), resource.DecimalSI)
|
||||||
}
|
}
|
||||||
|
@ -95,8 +95,9 @@ type resourceRequest struct {
|
|||||||
func getResourceRequest(pod *api.Pod) resourceRequest {
|
func getResourceRequest(pod *api.Pod) resourceRequest {
|
||||||
result := resourceRequest{}
|
result := resourceRequest{}
|
||||||
for ix := range pod.Spec.Containers {
|
for ix := range pod.Spec.Containers {
|
||||||
result.memory += pod.Spec.Containers[ix].Memory.Value()
|
limits := pod.Spec.Containers[ix].Resources.Limits
|
||||||
result.milliCPU += pod.Spec.Containers[ix].CPU.MilliValue()
|
result.memory += limits.Memory().Value()
|
||||||
|
result.milliCPU += limits.Cpu().MilliValue()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -120,8 +121,8 @@ func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node
|
|||||||
memoryRequested += existingRequest.memory
|
memoryRequested += existingRequest.memory
|
||||||
}
|
}
|
||||||
|
|
||||||
totalMilliCPU := info.Spec.Capacity.Get(api.ResourceCPU).MilliValue()
|
totalMilliCPU := info.Spec.Capacity.Cpu().MilliValue()
|
||||||
totalMemory := info.Spec.Capacity.Get(api.ResourceMemory).Value()
|
totalMemory := info.Spec.Capacity.Memory().Value()
|
||||||
|
|
||||||
fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU
|
fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU
|
||||||
fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory
|
fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory
|
||||||
|
@ -46,8 +46,8 @@ func (nodes FakeNodeListInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
|
|||||||
func makeResources(milliCPU int64, memory int64) api.NodeResources {
|
func makeResources(milliCPU int64, memory int64) api.NodeResources {
|
||||||
return api.NodeResources{
|
return api.NodeResources{
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
"cpu": *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
"memory": *resource.NewQuantity(memory, resource.BinarySI),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,8 +56,12 @@ func newResourcePod(usage ...resourceRequest) api.Pod {
|
|||||||
containers := []api.Container{}
|
containers := []api.Container{}
|
||||||
for _, req := range usage {
|
for _, req := range usage {
|
||||||
containers = append(containers, api.Container{
|
containers = append(containers, api.Container{
|
||||||
Memory: *resource.NewQuantity(req.memory, resource.BinarySI),
|
Resources: api.ResourceRequirementSpec{
|
||||||
CPU: *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
|
Limits: api.ResourceList{
|
||||||
|
"cpu": *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
|
||||||
|
"memory": *resource.NewQuantity(req.memory, resource.BinarySI),
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return api.Pod{
|
return api.Pod{
|
||||||
|
@ -42,19 +42,19 @@ func calculateOccupancy(pod api.Pod, node api.Node, pods []api.Pod) HostPriority
|
|||||||
totalMemory := int64(0)
|
totalMemory := int64(0)
|
||||||
for _, existingPod := range pods {
|
for _, existingPod := range pods {
|
||||||
for _, container := range existingPod.Spec.Containers {
|
for _, container := range existingPod.Spec.Containers {
|
||||||
totalMilliCPU += container.CPU.MilliValue()
|
totalMilliCPU += container.Resources.Limits.Cpu().MilliValue()
|
||||||
totalMemory += container.Memory.Value()
|
totalMemory += container.Resources.Limits.Memory().Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add the resources requested by the current pod being scheduled.
|
// Add the resources requested by the current pod being scheduled.
|
||||||
// This also helps differentiate between differently sized, but empty, minions.
|
// This also helps differentiate between differently sized, but empty, minions.
|
||||||
for _, container := range pod.Spec.Containers {
|
for _, container := range pod.Spec.Containers {
|
||||||
totalMilliCPU += container.CPU.MilliValue()
|
totalMilliCPU += container.Resources.Limits.Cpu().MilliValue()
|
||||||
totalMemory += container.Memory.Value()
|
totalMemory += container.Resources.Limits.Memory().Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
capacityMilliCPU := node.Spec.Capacity.Get(api.ResourceCPU).MilliValue()
|
capacityMilliCPU := node.Spec.Capacity.Cpu().MilliValue()
|
||||||
capacityMemory := node.Spec.Capacity.Get(api.ResourceMemory).Value()
|
capacityMemory := node.Spec.Capacity.Memory().Value()
|
||||||
|
|
||||||
cpuScore := calculateScore(totalMilliCPU, capacityMilliCPU, node.Name)
|
cpuScore := calculateScore(totalMilliCPU, capacityMilliCPU, node.Name)
|
||||||
memoryScore := calculateScore(totalMemory, capacityMemory, node.Name)
|
memoryScore := calculateScore(totalMemory, capacityMemory, node.Name)
|
||||||
|
@ -30,8 +30,8 @@ func makeMinion(node string, milliCPU, memory int64) api.Node {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: node},
|
ObjectMeta: api.ObjectMeta{Name: node},
|
||||||
Spec: api.NodeSpec{
|
Spec: api.NodeSpec{
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
"cpu": *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
"memory": *resource.NewQuantity(memory, resource.BinarySI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -57,14 +57,40 @@ func TestLeastRequested(t *testing.T) {
|
|||||||
}
|
}
|
||||||
cpuOnly := api.PodSpec{
|
cpuOnly := api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{CPU: resource.MustParse("1000m")},
|
{
|
||||||
{CPU: resource.MustParse("2000m")},
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
"cpu": resource.MustParse("1000m"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
"cpu": resource.MustParse("2000m"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cpuAndMemory := api.PodSpec{
|
cpuAndMemory := api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{CPU: resource.MustParse("1000m"), Memory: resource.MustParse("2000")},
|
{
|
||||||
{CPU: resource.MustParse("2000m"), Memory: resource.MustParse("3000")},
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
"cpu": resource.MustParse("1000m"),
|
||||||
|
"memory": resource.MustParse("2000"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resources: api.ResourceRequirementSpec{
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
"cpu": resource.MustParse("2000m"),
|
||||||
|
"memory": resource.MustParse("3000"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -103,8 +103,8 @@ func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) e
|
|||||||
|
|
||||||
for i := range pod.Spec.Containers {
|
for i := range pod.Spec.Containers {
|
||||||
container := pod.Spec.Containers[i]
|
container := pod.Spec.Containers[i]
|
||||||
containerCPU := container.CPU.MilliValue()
|
containerCPU := container.Resources.Limits.Cpu().MilliValue()
|
||||||
containerMem := container.Memory.Value()
|
containerMem := container.Resources.Limits.Memory().Value()
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
minContainerCPU = containerCPU
|
minContainerCPU = containerCPU
|
||||||
@ -113,8 +113,8 @@ func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) e
|
|||||||
maxContainerMem = containerMem
|
maxContainerMem = containerMem
|
||||||
}
|
}
|
||||||
|
|
||||||
podCPU = podCPU + container.CPU.MilliValue()
|
podCPU = podCPU + container.Resources.Limits.Cpu().MilliValue()
|
||||||
podMem = podMem + container.Memory.Value()
|
podMem = podMem + container.Resources.Limits.Memory().Value()
|
||||||
|
|
||||||
minContainerCPU = Min(containerCPU, minContainerCPU)
|
minContainerCPU = Min(containerCPU, minContainerCPU)
|
||||||
minContainerMem = Min(containerMem, minContainerMem)
|
minContainerMem = Min(containerMem, minContainerMem)
|
||||||
|
@ -23,6 +23,19 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getResourceRequirements(cpu, memory string) api.ResourceRequirementSpec {
|
||||||
|
res := api.ResourceRequirementSpec{}
|
||||||
|
res.Limits = api.ResourceList{}
|
||||||
|
if cpu != "" {
|
||||||
|
res.Limits[api.ResourceCPU] = resource.MustParse(cpu)
|
||||||
|
}
|
||||||
|
if memory != "" {
|
||||||
|
res.Limits[api.ResourceMemory] = resource.MustParse(memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func TestPodLimitFunc(t *testing.T) {
|
func TestPodLimitFunc(t *testing.T) {
|
||||||
limitRange := &api.LimitRange{
|
limitRange := &api.LimitRange{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
@ -32,25 +45,13 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Limits: []api.LimitRangeItem{
|
Limits: []api.LimitRangeItem{
|
||||||
{
|
{
|
||||||
Type: api.LimitTypePod,
|
Type: api.LimitTypePod,
|
||||||
Max: api.ResourceList{
|
Max: getResourceRequirements("200m", "4Gi").Limits,
|
||||||
api.ResourceCPU: resource.MustParse("200m"),
|
Min: getResourceRequirements("50m", "2Mi").Limits,
|
||||||
api.ResourceMemory: resource.MustParse("4Gi"),
|
|
||||||
},
|
|
||||||
Min: api.ResourceList{
|
|
||||||
api.ResourceCPU: resource.MustParse("50m"),
|
|
||||||
api.ResourceMemory: resource.MustParse("2Mi"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: api.LimitTypeContainer,
|
Type: api.LimitTypeContainer,
|
||||||
Max: api.ResourceList{
|
Max: getResourceRequirements("100m", "2Gi").Limits,
|
||||||
api.ResourceCPU: resource.MustParse("100m"),
|
Min: getResourceRequirements("25m", "1Mi").Limits,
|
||||||
api.ResourceMemory: resource.MustParse("2Gi"),
|
|
||||||
},
|
|
||||||
Min: api.ResourceList{
|
|
||||||
api.ResourceCPU: resource.MustParse("25m"),
|
|
||||||
api.ResourceMemory: resource.MustParse("1Mi"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -62,14 +63,12 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "foo:V1",
|
Image: "foo:V1",
|
||||||
CPU: resource.MustParse("100m"),
|
Resources: getResourceRequirements("100m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("100m"),
|
Resources: getResourceRequirements("100m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -79,9 +78,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("100m"),
|
Resources: getResourceRequirements("100m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -94,9 +92,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("25m"),
|
Resources: getResourceRequirements("25m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -106,9 +103,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("110m"),
|
Resources: getResourceRequirements("110m", "1Gi"),
|
||||||
Memory: resource.MustParse("1Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -118,9 +114,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("30m"),
|
Resources: getResourceRequirements("30m", "0"),
|
||||||
Memory: resource.MustParse("0"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -130,9 +125,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("30m"),
|
Resources: getResourceRequirements("30m", "3Gi"),
|
||||||
Memory: resource.MustParse("3Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -142,9 +136,8 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("40m"),
|
Resources: getResourceRequirements("40m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -154,24 +147,20 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "1Mi"),
|
||||||
Memory: resource.MustParse("1Mi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V2",
|
Image: "boo:V2",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "1Mi"),
|
||||||
Memory: resource.MustParse("1Mi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V3",
|
Image: "boo:V3",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "1Mi"),
|
||||||
Memory: resource.MustParse("1Mi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V4",
|
Image: "boo:V4",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "1Mi"),
|
||||||
Memory: resource.MustParse("1Mi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -181,19 +170,16 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V2",
|
Image: "boo:V2",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V3",
|
Image: "boo:V3",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "2Gi"),
|
||||||
Memory: resource.MustParse("2Gi"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -203,19 +189,16 @@ func TestPodLimitFunc(t *testing.T) {
|
|||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "boo:V1",
|
Image: "boo:V1",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "0"),
|
||||||
Memory: resource.MustParse("0"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V2",
|
Image: "boo:V2",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "0"),
|
||||||
Memory: resource.MustParse("0"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Image: "boo:V3",
|
Image: "boo:V3",
|
||||||
CPU: resource.MustParse("60m"),
|
Resources: getResourceRequirements("60m", "0"),
|
||||||
Memory: resource.MustParse("0"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -55,11 +55,12 @@ func (resourceDefaults) Admit(a admission.Attributes) (err error) {
|
|||||||
obj := a.GetObject()
|
obj := a.GetObject()
|
||||||
pod := obj.(*api.Pod)
|
pod := obj.(*api.Pod)
|
||||||
for index := range pod.Spec.Containers {
|
for index := range pod.Spec.Containers {
|
||||||
if pod.Spec.Containers[index].Memory.Value() == 0 {
|
pod.Spec.Containers[index].Resources.Limits = api.ResourceList{}
|
||||||
pod.Spec.Containers[index].Memory = resource.MustParse(defaultMemory)
|
if pod.Spec.Containers[index].Resources.Limits.Memory().Value() == 0 {
|
||||||
|
pod.Spec.Containers[index].Resources.Limits[api.ResourceMemory] = resource.MustParse(defaultMemory)
|
||||||
}
|
}
|
||||||
if pod.Spec.Containers[index].CPU.Value() == 0 {
|
if pod.Spec.Containers[index].Resources.Limits.Cpu().Value() == 0 {
|
||||||
pod.Spec.Containers[index].CPU = resource.MustParse(defaultCPU)
|
pod.Spec.Containers[index].Resources.Limits[api.ResourceCPU] = resource.MustParse(defaultCPU)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -41,11 +41,13 @@ func TestAdmission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range pod.Spec.Containers {
|
for i := range pod.Spec.Containers {
|
||||||
if pod.Spec.Containers[i].Memory.String() != "512Mi" {
|
memory := pod.Spec.Containers[i].Resources.Limits.Memory().String()
|
||||||
t.Errorf("Unexpected memory value %s", pod.Spec.Containers[i].Memory.String())
|
cpu := pod.Spec.Containers[i].Resources.Limits.Cpu().String()
|
||||||
|
if memory != "512Mi" {
|
||||||
|
t.Errorf("Unexpected memory value %s", memory)
|
||||||
}
|
}
|
||||||
if pod.Spec.Containers[i].CPU.String() != "1" {
|
if cpu != "1" {
|
||||||
t.Errorf("Unexpected cpu value %s", pod.Spec.Containers[i].CPU.String())
|
t.Errorf("Unexpected cpu value %s", cpu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,19 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getResourceRequirements(cpu, memory string) api.ResourceRequirementSpec {
|
||||||
|
res := api.ResourceRequirementSpec{}
|
||||||
|
res.Limits = api.ResourceList{}
|
||||||
|
if cpu != "" {
|
||||||
|
res.Limits[api.ResourceCPU] = resource.MustParse(cpu)
|
||||||
|
}
|
||||||
|
if memory != "" {
|
||||||
|
res.Limits[api.ResourceMemory] = resource.MustParse(memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdmissionIgnoresDelete(t *testing.T) {
|
func TestAdmissionIgnoresDelete(t *testing.T) {
|
||||||
namespace := "default"
|
namespace := "default"
|
||||||
handler := NewResourceQuota(&client.Fake{})
|
handler := NewResourceQuota(&client.Fake{})
|
||||||
@ -43,7 +56,7 @@ func TestIncrementUsagePods(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -78,7 +91,7 @@ func TestIncrementUsageMemory(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -96,7 +109,7 @@ func TestIncrementUsageMemory(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
}}
|
}}
|
||||||
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,7 +134,7 @@ func TestExceedUsageMemory(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -139,7 +152,7 @@ func TestExceedUsageMemory(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("3Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "3Gi")}},
|
||||||
}}
|
}}
|
||||||
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -156,7 +169,7 @@ func TestIncrementUsageCPU(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -174,7 +187,7 @@ func TestIncrementUsageCPU(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
}}
|
}}
|
||||||
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -199,7 +212,7 @@ func TestExceedUsageCPU(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -217,7 +230,7 @@ func TestExceedUsageCPU(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("500m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("500m", "1Gi")}},
|
||||||
}}
|
}}
|
||||||
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -234,7 +247,7 @@ func TestExceedUsagePods(t *testing.T) {
|
|||||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "vol"}},
|
Volumes: []api.Volume{{Name: "vol"}},
|
||||||
Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}},
|
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user