Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding an extensible resource spec to the API. #3841

Merged
merged 1 commit into from
Feb 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions pkg/api/resource_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pkg/api/resource/helpers.go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains methods on structs defined in this package. That is the reason for placing them in 'package api'

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 changes: 53 additions & 0 deletions pkg/api/resource_helpers_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
20 changes: 11 additions & 9 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We don't say "Spec" on any other types except the ones that correspond to a "spec" field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I gave up on changing those. We could make this consistent.

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.
Expand Down Expand Up @@ -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)
Expand Down
135 changes: 134 additions & 1 deletion pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions pkg/api/v1beta1/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
16 changes: 11 additions & 5 deletions pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. This should resolve #2264, or at least the main problem.

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"`
Expand Down