Merge pull request #3841 from vishh/api_resources
Adding an extensible resource spec to the API.
This commit is contained in:
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"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
type Container struct {
|
||||
// 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"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Env []EnvVar `json:"env,omitempty"`
|
||||
// Optional: Defaults to unlimited.
|
||||
Memory resource.Quantity `json:"memory,omitempty"`
|
||||
// Optional: Defaults to unlimited.
|
||||
CPU resource.Quantity `json:"cpu,omitempty"`
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||
// Compute resource requirements.
|
||||
Resources ResourceRequirementSpec `json:"resources,omitempty"`
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||
// Optional: Default to false.
|
||||
@@ -775,8 +779,6 @@ type NodeResources struct {
|
||||
type ResourceName string
|
||||
|
||||
const (
|
||||
// The default compute resource namespace for all standard resource types.
|
||||
DefaultResourceNamespace = "kubernetes.io"
|
||||
// CPU, in cores. (500m = .5 cores)
|
||||
ResourceCPU ResourceName = "cpu"
|
||||
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
||||
|
@@ -444,7 +444,140 @@ func init() {
|
||||
}
|
||||
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 {
|
||||
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
||||
return err
|
||||
|
@@ -22,7 +22,9 @@ import (
|
||||
"testing"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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.
|
||||
type Container struct {
|
||||
// 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.
|
||||
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.
|
||||
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"`
|
||||
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
||||
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"`
|
||||
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.
|
||||
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.
|
||||
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"`
|
||||
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"`
|
||||
|
@@ -295,7 +295,143 @@ func init() {
|
||||
}
|
||||
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 {
|
||||
if err := s.Convert(&in.Volumes, &out.Volumes, 0); err != nil {
|
||||
return err
|
||||
|
@@ -21,7 +21,9 @@ import (
|
||||
"testing"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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.
|
||||
type Container struct {
|
||||
// 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.
|
||||
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.
|
||||
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"`
|
||||
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container"`
|
||||
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"`
|
||||
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.
|
||||
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.
|
||||
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"`
|
||||
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"`
|
||||
|
@@ -322,6 +322,12 @@ type Capabilities struct {
|
||||
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.
|
||||
type Container struct {
|
||||
// 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.
|
||||
Command []string `json:"command,omitempty"`
|
||||
// Optional: Defaults to Docker's default.
|
||||
WorkingDir string `json:"workingDir,omitempty"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Env []EnvVar `json:"env,omitempty"`
|
||||
// Optional: Defaults to unlimited. Units: bytes.
|
||||
Memory resource.Quantity `json:"memory,omitempty"`
|
||||
// Optional: Defaults to unlimited. Units: Cores. (500m == 1/2 core)
|
||||
CPU resource.Quantity `json:"cpu,omitempty"`
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||
WorkingDir string `json:"workingDir,omitempty"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Env []EnvVar `json:"env,omitempty"`
|
||||
Resources ResourceRequirementSpec `json:"resources,omitempty" description:"Compute Resources required by this container"`
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
|
||||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||
// Optional: Default to false.
|
||||
@@ -796,8 +799,6 @@ type NodeCondition struct {
|
||||
type ResourceName string
|
||||
|
||||
const (
|
||||
// The default compute resource namespace for all standard resource types.
|
||||
DefaultResourceNamespace = "kubernetes.io"
|
||||
// CPU, in cores. (500m = .5 cores)
|
||||
ResourceCPU ResourceName = "cpu"
|
||||
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
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/labels"
|
||||
"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, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
|
||||
cErrs = append(cErrs, validatePullPolicyWithDefault(ctr).Prefix("pullPolicy")...)
|
||||
cErrs = append(cErrs, validateResourceRequirements(ctr).Prefix("resources")...)
|
||||
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
||||
}
|
||||
// Check for colliding ports across all containers.
|
||||
@@ -696,28 +698,17 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// Typename is a generic representation for all compute resource typenames.
|
||||
// Validate compute resource typename.
|
||||
// Refer to docs/resources.md for more details.
|
||||
func ValidateResourceName(str string) errs.ValidationErrorList {
|
||||
func validateResourceName(str string) errs.ValidationErrorList {
|
||||
if !util.IsQualifiedName(str) {
|
||||
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename format %q", str)}
|
||||
}
|
||||
|
||||
parts := strings.Split(str, "/")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
if !api.IsStandardResourceName(parts[0]) {
|
||||
if len(strings.Split(str, "/")) == 1 {
|
||||
if !api.IsStandardResourceName(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{}
|
||||
@@ -740,15 +731,37 @@ func ValidateLimitRange(limitRange *api.LimitRange) errs.ValidationErrorList {
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
for k := range limit.Max {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||
}
|
||||
for k := range limit.Min {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||
}
|
||||
}
|
||||
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.
|
||||
func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
@@ -763,13 +776,13 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", resourceQuota.Namespace, ""))
|
||||
}
|
||||
for k := range resourceQuota.Spec.Hard {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||
}
|
||||
for k := range resourceQuota.Status.Hard {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||
}
|
||||
for k := range resourceQuota.Status.Used {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
allErrs = append(allErrs, validateResourceName(string(k))...)
|
||||
}
|
||||
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) {
|
||||
volumes := util.StringSet{}
|
||||
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},
|
||||
}
|
||||
if errs := validateContainers(successCase, volumes); len(errs) != 0 {
|
||||
@@ -349,6 +367,35 @@ func TestValidateContainers(t *testing.T) {
|
||||
"privilege disabled": {
|
||||
{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 {
|
||||
if errs := validateContainers(v, volumes); len(errs) == 0 {
|
||||
@@ -422,8 +469,12 @@ func TestValidateManifest(t *testing.T) {
|
||||
Image: "image",
|
||||
Command: []string{"foo", "bar"},
|
||||
WorkingDir: "/tmp",
|
||||
Memory: resource.MustParse("1"),
|
||||
CPU: resource.MustParse("1"),
|
||||
Resources: api.ResourceRequirementSpec{
|
||||
Limits: api.ResourceList{
|
||||
"cpu": resource.MustParse("1"),
|
||||
"memory": resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
Ports: []api.Port{
|
||||
{Name: "p1", ContainerPort: 80, HostPort: 8080},
|
||||
{Name: "p2", ContainerPort: 81},
|
||||
@@ -711,7 +762,9 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
Containers: []api.Container{
|
||||
{
|
||||
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{
|
||||
{
|
||||
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},
|
||||
{"kubernetes.io/cpu", true},
|
||||
{"kubernetes.io/disk", false},
|
||||
{"my.favorite.app.co/12345", true},
|
||||
{"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},
|
||||
}
|
||||
for _, item := range table {
|
||||
err := ValidateResourceName(item.input)
|
||||
err := validateResourceName(item.input)
|
||||
if len(err) != 0 && item.success {
|
||||
t.Errorf("expected no failure for input %q", item.input)
|
||||
} else if len(err) == 0 && !item.success {
|
||||
|
Reference in New Issue
Block a user