mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #3751 from derekwaynecarr/limit_range
Admission Control: LimitRange
This commit is contained in:
commit
b3fffe0efb
@ -28,5 +28,6 @@ import (
|
||||
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/limitranger"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcedefaults"
|
||||
)
|
||||
|
18
examples/limitrange/invalid-pod.json
Normal file
18
examples/limitrange/invalid-pod.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "invalid-pod",
|
||||
"kind": "Pod",
|
||||
"apiVersion":"v1beta2",
|
||||
"labels": {
|
||||
"name": "invalid-pod"
|
||||
},
|
||||
"desiredState": {
|
||||
"manifest": {
|
||||
"version": "v1beta1",
|
||||
"id": "invalid-pod",
|
||||
"containers": [{
|
||||
"name": "kubernetes-serve-hostname",
|
||||
"image": "kubernetes/serve_hostname",
|
||||
}]
|
||||
}
|
||||
},
|
||||
}
|
31
examples/limitrange/limit-range.json
Normal file
31
examples/limitrange/limit-range.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"id": "limits",
|
||||
"kind": "LimitRange",
|
||||
"apiVersion": "v1beta1",
|
||||
"spec": {
|
||||
"limits": [
|
||||
{
|
||||
"type": "Pod",
|
||||
"max": {
|
||||
"memory": "1073741824",
|
||||
"cpu": "2",
|
||||
},
|
||||
"min": {
|
||||
"memory": "1048576",
|
||||
"cpu": "0.25"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"max": {
|
||||
"memory": "1073741824",
|
||||
"cpu": "2",
|
||||
},
|
||||
"min": {
|
||||
"memory": "1048576",
|
||||
"cpu": "0.25"
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
20
examples/limitrange/valid-pod.json
Normal file
20
examples/limitrange/valid-pod.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "valid-pod",
|
||||
"kind": "Pod",
|
||||
"apiVersion":"v1beta2",
|
||||
"labels": {
|
||||
"name": "valid-pod"
|
||||
},
|
||||
"desiredState": {
|
||||
"manifest": {
|
||||
"version": "v1beta1",
|
||||
"id": "invalid-pod",
|
||||
"containers": [{
|
||||
"name": "kubernetes-serve-hostname",
|
||||
"image": "kubernetes/serve_hostname",
|
||||
"cpu": 1000,
|
||||
"memory": 1048576,
|
||||
}]
|
||||
}
|
||||
},
|
||||
}
|
@ -47,6 +47,8 @@ func init() {
|
||||
&BoundPod{},
|
||||
&BoundPods{},
|
||||
&List{},
|
||||
&LimitRange{},
|
||||
&LimitRangeList{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
||||
@ -77,3 +79,5 @@ func (*ContainerManifestList) IsAnAPIObject() {}
|
||||
func (*BoundPod) IsAnAPIObject() {}
|
||||
func (*BoundPods) IsAnAPIObject() {}
|
||||
func (*List) IsAnAPIObject() {}
|
||||
func (*LimitRange) IsAnAPIObject() {}
|
||||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
|
@ -1138,3 +1138,47 @@ type List struct {
|
||||
|
||||
Items []runtime.Object `json:"items"`
|
||||
}
|
||||
|
||||
// A type of object that is limited
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// Limit that applies to all pods in a namespace
|
||||
LimitTypePod LimitType = "Pod"
|
||||
// Limit that applies to all containers in a namespace
|
||||
LimitTypeContainer LimitType = "Container"
|
||||
)
|
||||
|
||||
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind
|
||||
type LimitRangeItem struct {
|
||||
// Type of resource that this limit applies to
|
||||
Type LimitType `json:"type,omitempty"`
|
||||
// Max usage constraints on this kind by resource name
|
||||
Max ResourceList `json:"max,omitempty"`
|
||||
// Min usage constraints on this kind by resource name
|
||||
Min ResourceList `json:"min,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeSpec defines a min/max usage limit for resources that match on kind
|
||||
type LimitRangeSpec struct {
|
||||
// Limits is the list of LimitRangeItem objects that are enforced
|
||||
Limits []LimitRangeItem `json:"limits"`
|
||||
}
|
||||
|
||||
// LimitRange sets resource usage limits for each kind of resource in a Namespace
|
||||
type LimitRange struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec defines the limits enforced
|
||||
Spec LimitRangeSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeList is a list of LimitRange items.
|
||||
type LimitRangeList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Items is a list of LimitRange objects
|
||||
Items []LimitRange `json:"items"`
|
||||
}
|
||||
|
@ -568,7 +568,72 @@ func init() {
|
||||
out.Status.HostIP = in.HostIP
|
||||
return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0)
|
||||
},
|
||||
|
||||
func(in *newer.LimitRange, out *LimitRange, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ObjectMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRange, out *newer.LimitRange, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = LimitRangeSpec{}
|
||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
for i := range in.Limits {
|
||||
if err := s.Convert(&in.Limits[i], &out.Limits[i], 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRangeSpec, out *newer.LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = newer.LimitRangeSpec{}
|
||||
out.Limits = make([]newer.LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
for i := range in.Limits {
|
||||
if err := s.Convert(&in.Limits[i], &out.Limits[i], 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error {
|
||||
*out = LimitRangeItem{}
|
||||
out.Type = LimitType(in.Type)
|
||||
if err := s.Convert(&in.Max, &out.Max, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Min, &out.Min, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRangeItem, out *newer.LimitRangeItem, s conversion.Scope) error {
|
||||
*out = newer.LimitRangeItem{}
|
||||
out.Type = newer.LimitType(in.Type)
|
||||
if err := s.Convert(&in.Max, &out.Max, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Min, &out.Min, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Object ID <-> Name
|
||||
// TODO: amend the conversion package to allow overriding specific fields.
|
||||
func(in *ObjectReference, out *newer.ObjectReference, s conversion.Scope) error {
|
||||
|
@ -48,6 +48,8 @@ func init() {
|
||||
&BoundPod{},
|
||||
&BoundPods{},
|
||||
&List{},
|
||||
&LimitRange{},
|
||||
&LimitRangeList{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
||||
@ -78,3 +80,5 @@ func (*ContainerManifestList) IsAnAPIObject() {}
|
||||
func (*BoundPod) IsAnAPIObject() {}
|
||||
func (*BoundPods) IsAnAPIObject() {}
|
||||
func (*List) IsAnAPIObject() {}
|
||||
func (*LimitRange) IsAnAPIObject() {}
|
||||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
|
@ -904,3 +904,45 @@ type List struct {
|
||||
TypeMeta `json:",inline"`
|
||||
Items []runtime.RawExtension `json:"items" description:"list of objects"`
|
||||
}
|
||||
|
||||
// A type of object that is limited
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// Limit that applies to all pods in a namespace
|
||||
LimitTypePod LimitType = "Pod"
|
||||
// Limit that applies to all containers in a namespace
|
||||
LimitTypeContainer LimitType = "Container"
|
||||
)
|
||||
|
||||
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind
|
||||
type LimitRangeItem struct {
|
||||
// Type of resource that this limit applies to
|
||||
Type LimitType `json:"type,omitempty"`
|
||||
// Max usage constraints on this kind by resource name
|
||||
Max ResourceList `json:"max,omitempty"`
|
||||
// Min usage constraints on this kind by resource name
|
||||
Min ResourceList `json:"min,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeSpec defines a min/max usage limit for resources that match on kind
|
||||
type LimitRangeSpec struct {
|
||||
// Limits is the list of LimitRangeItem objects that are enforced
|
||||
Limits []LimitRangeItem `json:"limits"`
|
||||
}
|
||||
|
||||
// LimitRange sets resource usage limits for each kind of resource in a Namespace
|
||||
type LimitRange struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Spec defines the limits enforced
|
||||
Spec LimitRangeSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeList is a list of LimitRange items.
|
||||
type LimitRangeList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Items is a list of LimitRange objects
|
||||
Items []LimitRange `json:"items"`
|
||||
}
|
||||
|
@ -485,7 +485,72 @@ func init() {
|
||||
out.Status.HostIP = in.HostIP
|
||||
return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0)
|
||||
},
|
||||
|
||||
func(in *newer.LimitRange, out *LimitRange, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ObjectMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRange, out *newer.LimitRange, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = LimitRangeSpec{}
|
||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
for i := range in.Limits {
|
||||
if err := s.Convert(&in.Limits[i], &out.Limits[i], 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRangeSpec, out *newer.LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = newer.LimitRangeSpec{}
|
||||
out.Limits = make([]newer.LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
for i := range in.Limits {
|
||||
if err := s.Convert(&in.Limits[i], &out.Limits[i], 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error {
|
||||
*out = LimitRangeItem{}
|
||||
out.Type = LimitType(in.Type)
|
||||
if err := s.Convert(&in.Max, &out.Max, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Min, &out.Min, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *LimitRangeItem, out *newer.LimitRangeItem, s conversion.Scope) error {
|
||||
*out = newer.LimitRangeItem{}
|
||||
out.Type = newer.LimitType(in.Type)
|
||||
if err := s.Convert(&in.Max, &out.Max, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Min, &out.Min, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Object ID <-> Name
|
||||
// TODO: amend the conversion package to allow overriding specific fields.
|
||||
func(in *ObjectReference, out *newer.ObjectReference, s conversion.Scope) error {
|
||||
|
@ -48,6 +48,8 @@ func init() {
|
||||
&BoundPod{},
|
||||
&BoundPods{},
|
||||
&List{},
|
||||
&LimitRange{},
|
||||
&LimitRangeList{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
||||
@ -78,3 +80,5 @@ func (*ContainerManifestList) IsAnAPIObject() {}
|
||||
func (*BoundPod) IsAnAPIObject() {}
|
||||
func (*BoundPods) IsAnAPIObject() {}
|
||||
func (*List) IsAnAPIObject() {}
|
||||
func (*LimitRange) IsAnAPIObject() {}
|
||||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
|
@ -906,3 +906,45 @@ type List struct {
|
||||
TypeMeta `json:",inline"`
|
||||
Items []runtime.RawExtension `json:"items" description:"list of objects"`
|
||||
}
|
||||
|
||||
// A type of object that is limited
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// Limit that applies to all pods in a namespace
|
||||
LimitTypePod LimitType = "Pod"
|
||||
// Limit that applies to all containers in a namespace
|
||||
LimitTypeContainer LimitType = "Container"
|
||||
)
|
||||
|
||||
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind
|
||||
type LimitRangeItem struct {
|
||||
// Type of resource that this limit applies to
|
||||
Type LimitType `json:"type,omitempty"`
|
||||
// Max usage constraints on this kind by resource name
|
||||
Max ResourceList `json:"max,omitempty"`
|
||||
// Min usage constraints on this kind by resource name
|
||||
Min ResourceList `json:"min,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeSpec defines a min/max usage limit for resources that match on kind
|
||||
type LimitRangeSpec struct {
|
||||
// Limits is the list of LimitRangeItem objects that are enforced
|
||||
Limits []LimitRangeItem `json:"limits"`
|
||||
}
|
||||
|
||||
// LimitRange sets resource usage limits for each kind of resource in a Namespace
|
||||
type LimitRange struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Spec defines the limits enforced
|
||||
Spec LimitRangeSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeList is a list of LimitRange items.
|
||||
type LimitRangeList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Items is a list of LimitRange objects
|
||||
Items []LimitRange `json:"items"`
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ func init() {
|
||||
&Event{},
|
||||
&EventList{},
|
||||
&List{},
|
||||
&LimitRange{},
|
||||
&LimitRangeList{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
||||
@ -78,3 +80,5 @@ func (*OperationList) IsAnAPIObject() {}
|
||||
func (*Event) IsAnAPIObject() {}
|
||||
func (*EventList) IsAnAPIObject() {}
|
||||
func (*List) IsAnAPIObject() {}
|
||||
func (*LimitRange) IsAnAPIObject() {}
|
||||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
|
@ -1066,3 +1066,47 @@ type List struct {
|
||||
|
||||
Items []runtime.RawExtension `json:"items" description:"list of objects"`
|
||||
}
|
||||
|
||||
// A type of object that is limited
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// Limit that applies to all pods in a namespace
|
||||
LimitTypePod LimitType = "Pod"
|
||||
// Limit that applies to all containers in a namespace
|
||||
LimitTypeContainer LimitType = "Container"
|
||||
)
|
||||
|
||||
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind
|
||||
type LimitRangeItem struct {
|
||||
// Type of resource that this limit applies to
|
||||
Type LimitType `json:"type,omitempty"`
|
||||
// Max usage constraints on this kind by resource name
|
||||
Max ResourceList `json:"max,omitempty"`
|
||||
// Min usage constraints on this kind by resource name
|
||||
Min ResourceList `json:"min,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeSpec defines a min/max usage limit for resources that match on kind
|
||||
type LimitRangeSpec struct {
|
||||
// Limits is the list of LimitRangeItem objects that are enforced
|
||||
Limits []LimitRangeItem `json:"limits"`
|
||||
}
|
||||
|
||||
// LimitRange sets resource usage limits for each kind of resource in a Namespace
|
||||
type LimitRange struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec defines the limits enforced
|
||||
Spec LimitRangeSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// LimitRangeList is a list of LimitRange items.
|
||||
type LimitRangeList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Items is a list of LimitRange objects
|
||||
Items []LimitRange `json:"items"`
|
||||
}
|
||||
|
@ -640,3 +640,29 @@ func ValidateResourceName(str string) errs.ValidationErrorList {
|
||||
|
||||
return errs.ValidationErrorList{}
|
||||
}
|
||||
|
||||
// ValidateLimitRange tests if required fields in the LimitRange are set.
|
||||
func ValidateLimitRange(limitRange *api.LimitRange) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if len(limitRange.Name) == 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("name", limitRange.Name))
|
||||
} else if !util.IsDNSSubdomain(limitRange.Name) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("name", limitRange.Name, ""))
|
||||
}
|
||||
if len(limitRange.Namespace) == 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("namespace", limitRange.Namespace))
|
||||
} else if !util.IsDNSSubdomain(limitRange.Namespace) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", limitRange.Namespace, ""))
|
||||
}
|
||||
// ensure resource names are properly qualified per docs/resources.md
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
for k := range limit.Max {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
}
|
||||
for k := range limit.Min {
|
||||
allErrs = append(allErrs, ValidateResourceName(string(k))...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
@ -1552,3 +1552,92 @@ func TestValidateResourceNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLimitRange(t *testing.T) {
|
||||
successCases := []api.LimitRange{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, successCase := range successCases {
|
||||
if errs := ValidateLimitRange(&successCase); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]api.LimitRange{
|
||||
"zero-length Name": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"zero-length-namespace": {
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateLimitRange(&v)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
}
|
||||
for i := range errs {
|
||||
field := errs[i].(*errors.ValidationError).Field
|
||||
if field != "name" &&
|
||||
field != "namespace" {
|
||||
t.Errorf("%s: missing prefix for: %v", k, errs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ type Interface interface {
|
||||
VersionInterface
|
||||
NodesInterface
|
||||
EventNamespacer
|
||||
LimitRangesNamespacer
|
||||
}
|
||||
|
||||
func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||
@ -63,6 +64,10 @@ func (c *Client) Services(namespace string) ServiceInterface {
|
||||
return newServices(c, namespace)
|
||||
}
|
||||
|
||||
func (c *Client) LimitRanges(namespace string) LimitRangeInterface {
|
||||
return newLimitRanges(c, namespace)
|
||||
}
|
||||
|
||||
// VersionInterface has a method to retrieve the server version.
|
||||
type VersionInterface interface {
|
||||
ServerVersion() (*version.Info, error)
|
||||
|
@ -34,15 +34,20 @@ type FakeAction struct {
|
||||
// Fake implements Interface. Meant to be embedded into a struct to get a default
|
||||
// implementation. This makes faking out just the method you want to test easier.
|
||||
type Fake struct {
|
||||
Actions []FakeAction
|
||||
PodsList api.PodList
|
||||
Ctrl api.ReplicationController
|
||||
ServiceList api.ServiceList
|
||||
EndpointsList api.EndpointsList
|
||||
MinionsList api.NodeList
|
||||
EventsList api.EventList
|
||||
Err error
|
||||
Watch watch.Interface
|
||||
Actions []FakeAction
|
||||
PodsList api.PodList
|
||||
Ctrl api.ReplicationController
|
||||
ServiceList api.ServiceList
|
||||
EndpointsList api.EndpointsList
|
||||
MinionsList api.NodeList
|
||||
EventsList api.EventList
|
||||
LimitRangesList api.LimitRangeList
|
||||
Err error
|
||||
Watch watch.Interface
|
||||
}
|
||||
|
||||
func (c *Fake) LimitRanges(namespace string) LimitRangeInterface {
|
||||
return &FakeLimitRanges{Fake: c, Namespace: namespace}
|
||||
}
|
||||
|
||||
func (c *Fake) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||
|
54
pkg/client/fake_limit_ranges.go
Normal file
54
pkg/client/fake_limit_ranges.go
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
// FakeLimitRanges implements PodsInterface. Meant to be embedded into a struct to get a default
|
||||
// implementation. This makes faking out just the methods you want to test easier.
|
||||
type FakeLimitRanges struct {
|
||||
Fake *Fake
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func (c *FakeLimitRanges) List(selector labels.Selector) (*api.LimitRangeList, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-limitRanges"})
|
||||
return api.Scheme.CopyOrDie(&c.Fake.LimitRangesList).(*api.LimitRangeList), nil
|
||||
}
|
||||
|
||||
func (c *FakeLimitRanges) Get(name string) (*api.LimitRange, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-limitRange", Value: name})
|
||||
return &api.LimitRange{ObjectMeta: api.ObjectMeta{Name: name, Namespace: c.Namespace}}, nil
|
||||
}
|
||||
|
||||
func (c *FakeLimitRanges) Delete(name string) error {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "delete-limitRange", Value: name})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeLimitRanges) Create(limitRange *api.LimitRange) (*api.LimitRange, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "create-limitRange"})
|
||||
return &api.LimitRange{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeLimitRanges) Update(limitRange *api.LimitRange) (*api.LimitRange, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-limitRange", Value: limitRange.Name})
|
||||
return &api.LimitRange{}, nil
|
||||
}
|
94
pkg/client/limit_ranges.go
Normal file
94
pkg/client/limit_ranges.go
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
// LimitRangesNamespacer has methods to work with LimitRange resources in a namespace
|
||||
type LimitRangesNamespacer interface {
|
||||
LimitRanges(namespace string) LimitRangeInterface
|
||||
}
|
||||
|
||||
// LimitRangeInterface has methods to work with LimitRange resources.
|
||||
type LimitRangeInterface interface {
|
||||
List(selector labels.Selector) (*api.LimitRangeList, error)
|
||||
Get(name string) (*api.LimitRange, error)
|
||||
Delete(name string) error
|
||||
Create(limitRange *api.LimitRange) (*api.LimitRange, error)
|
||||
Update(limitRange *api.LimitRange) (*api.LimitRange, error)
|
||||
}
|
||||
|
||||
// limitRanges implements LimitRangesNamespacer interface
|
||||
type limitRanges struct {
|
||||
r *Client
|
||||
ns string
|
||||
}
|
||||
|
||||
// newLimitRanges returns a limitRanges
|
||||
func newLimitRanges(c *Client, namespace string) *limitRanges {
|
||||
return &limitRanges{
|
||||
r: c,
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// List takes a selector, and returns the list of limitRanges that match that selector.
|
||||
func (c *limitRanges) List(selector labels.Selector) (result *api.LimitRangeList, err error) {
|
||||
result = &api.LimitRangeList{}
|
||||
err = c.r.Get().Namespace(c.ns).Resource("limitRanges").SelectorParam("labels", selector).Do().Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Get takes the name of the limitRange, and returns the corresponding Pod object, and an error if it occurs
|
||||
func (c *limitRanges) Get(name string) (result *api.LimitRange, err error) {
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("name is required parameter to Get")
|
||||
}
|
||||
|
||||
result = &api.LimitRange{}
|
||||
err = c.r.Get().Namespace(c.ns).Resource("limitRanges").Name(name).Do().Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes the name of the limitRange, and returns an error if one occurs
|
||||
func (c *limitRanges) Delete(name string) error {
|
||||
return c.r.Delete().Namespace(c.ns).Resource("limitRanges").Name(name).Do().Error()
|
||||
}
|
||||
|
||||
// Create takes the representation of a limitRange. Returns the server's representation of the limitRange, and an error, if it occurs.
|
||||
func (c *limitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange, err error) {
|
||||
result = &api.LimitRange{}
|
||||
err = c.r.Post().Namespace(c.ns).Resource("limitRanges").Body(limitRange).Do().Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a limitRange to update. Returns the server's representation of the limitRange, and an error, if it occurs.
|
||||
func (c *limitRanges) Update(limitRange *api.LimitRange) (result *api.LimitRange, err error) {
|
||||
result = &api.LimitRange{}
|
||||
if len(limitRange.ResourceVersion) == 0 {
|
||||
err = fmt.Errorf("invalid update object, missing resource version: %v", limitRange)
|
||||
return
|
||||
}
|
||||
err = c.r.Put().Namespace(c.ns).Resource("limitRanges").Name(limitRange.Name).Body(limitRange).Do().Into(result)
|
||||
return
|
||||
}
|
194
pkg/client/limit_ranges_test.go
Normal file
194
pkg/client/limit_ranges_test.go
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
//"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
func TestLimitRangeCreate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "POST",
|
||||
Path: buildResourcePath(ns, "/limitRanges"),
|
||||
Query: buildQueryValues(ns, nil),
|
||||
Body: limitRange,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: limitRange},
|
||||
}
|
||||
|
||||
response, err := c.Setup().LimitRanges(ns).Create(limitRange)
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestLimitRangeGet(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "GET",
|
||||
Path: buildResourcePath(ns, "/limitRanges/abc"),
|
||||
Query: buildQueryValues(ns, nil),
|
||||
Body: nil,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: limitRange},
|
||||
}
|
||||
|
||||
response, err := c.Setup().LimitRanges(ns).Get("abc")
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestLimitRangeList(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
|
||||
limitRangeList := &api.LimitRangeList{
|
||||
Items: []api.LimitRange{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "GET",
|
||||
Path: buildResourcePath(ns, "/limitRanges"),
|
||||
Query: buildQueryValues(ns, nil),
|
||||
Body: nil,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: limitRangeList},
|
||||
}
|
||||
response, err := c.Setup().LimitRanges(ns).List(labels.Everything())
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestLimitRangeUpdate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/limitRanges/abc"), Query: buildQueryValues(ns, nil)},
|
||||
Response: Response{StatusCode: 200, Body: limitRange},
|
||||
}
|
||||
response, err := c.Setup().LimitRanges(ns).Update(limitRange)
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestInvalidLimitRangeUpdate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/limitRanges/abc"), Query: buildQueryValues(ns, nil)},
|
||||
Response: Response{StatusCode: 200, Body: limitRange},
|
||||
}
|
||||
_, err := c.Setup().LimitRanges(ns).Update(limitRange)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error due to missing ResourceVersion")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitRangeDelete(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/limitRanges/foo"), Query: buildQueryValues(ns, nil)},
|
||||
Response: Response{StatusCode: 200},
|
||||
}
|
||||
err := c.Setup().LimitRanges(ns).Delete("foo")
|
||||
c.Validate(t, nil, err)
|
||||
}
|
@ -47,10 +47,65 @@ func DescriberFor(kind string, c *client.Client) (Describer, bool) {
|
||||
return &ServiceDescriber{c}, true
|
||||
case "Minion", "Node":
|
||||
return &MinionDescriber{c}, true
|
||||
case "LimitRange":
|
||||
return &LimitRangeDescriber{c}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// LimitRangeDescriber generates information about a limit range
|
||||
type LimitRangeDescriber struct {
|
||||
client.Interface
|
||||
}
|
||||
|
||||
func (d *LimitRangeDescriber) Describe(namespace, name string) (string, error) {
|
||||
lr := d.LimitRanges(namespace)
|
||||
|
||||
limitRange, err := lr.Get(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tabbedString(func(out io.Writer) error {
|
||||
fmt.Fprintf(out, "Name:\t%s\n", limitRange.Name)
|
||||
fmt.Fprintf(out, "Type\tResource\tMin\tMax\n")
|
||||
fmt.Fprintf(out, "----\t--------\t---\t---\n")
|
||||
for i := range limitRange.Spec.Limits {
|
||||
item := limitRange.Spec.Limits[i]
|
||||
maxResources := item.Max
|
||||
minResources := item.Min
|
||||
|
||||
set := map[api.ResourceName]bool{}
|
||||
for k := range maxResources {
|
||||
set[k] = true
|
||||
}
|
||||
for k := range minResources {
|
||||
set[k] = true
|
||||
}
|
||||
|
||||
for k := range set {
|
||||
// if no value is set, we output -
|
||||
maxValue := "-"
|
||||
minValue := "-"
|
||||
|
||||
maxQuantity, maxQuantityFound := maxResources[k]
|
||||
if maxQuantityFound {
|
||||
maxValue = maxQuantity.String()
|
||||
}
|
||||
|
||||
minQuantity, minQuantityFound := minResources[k]
|
||||
if minQuantityFound {
|
||||
minValue = minQuantity.String()
|
||||
}
|
||||
|
||||
msg := "%v\t%v\t%v\t%v\n"
|
||||
fmt.Fprintf(out, msg, item.Type, k, minValue, maxValue)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PodDescriber generates information about a pod and the replication controllers that
|
||||
// create it.
|
||||
type PodDescriber struct {
|
||||
|
@ -143,11 +143,12 @@ func (e ShortcutExpander) VersionAndKindForResource(resource string) (defaultVer
|
||||
// indeed a shortcut. Otherwise, will return resource unmodified.
|
||||
func expandResourceShortcut(resource string) string {
|
||||
shortForms := map[string]string{
|
||||
"po": "pods",
|
||||
"rc": "replicationcontrollers",
|
||||
"se": "services",
|
||||
"mi": "minions",
|
||||
"ev": "events",
|
||||
"po": "pods",
|
||||
"rc": "replicationcontrollers",
|
||||
"se": "services",
|
||||
"mi": "minions",
|
||||
"ev": "events",
|
||||
"limits": "limitRanges",
|
||||
}
|
||||
if expanded, ok := shortForms[resource]; ok {
|
||||
return expanded
|
||||
|
@ -221,6 +221,7 @@ var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT"}
|
||||
var minionColumns = []string{"NAME", "LABELS", "STATUS"}
|
||||
var statusColumns = []string{"STATUS"}
|
||||
var eventColumns = []string{"TIME", "NAME", "KIND", "SUBOBJECT", "REASON", "SOURCE", "MESSAGE"}
|
||||
var limitRangeColumns = []string{"NAME"}
|
||||
|
||||
// addDefaultHandlers adds print handlers for default Kubernetes types.
|
||||
func (h *HumanReadablePrinter) addDefaultHandlers() {
|
||||
@ -235,6 +236,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
|
||||
h.Handler(statusColumns, printStatus)
|
||||
h.Handler(eventColumns, printEvent)
|
||||
h.Handler(eventColumns, printEventList)
|
||||
h.Handler(limitRangeColumns, printLimitRange)
|
||||
h.Handler(limitRangeColumns, printLimitRangeList)
|
||||
}
|
||||
|
||||
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
||||
@ -409,6 +412,24 @@ func printEventList(list *api.EventList, w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func printLimitRange(limitRange *api.LimitRange, w io.Writer) error {
|
||||
_, err := fmt.Fprintf(
|
||||
w, "%s\n",
|
||||
limitRange.Name,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prints the LimitRangeList in a human-friendly format.
|
||||
func printLimitRangeList(list *api.LimitRangeList, w io.Writer) error {
|
||||
for i := range list.Items {
|
||||
if err := printLimitRange(&list.Items[i], w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
|
||||
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
|
||||
w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0)
|
||||
|
@ -47,6 +47,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
|
||||
@ -109,6 +110,7 @@ type Master struct {
|
||||
minionRegistry minion.Registry
|
||||
bindingRegistry binding.Registry
|
||||
eventRegistry generic.Registry
|
||||
limitRangeRegistry generic.Registry
|
||||
storage map[string]apiserver.RESTStorage
|
||||
client *client.Client
|
||||
portalNet *net.IPNet
|
||||
@ -248,6 +250,7 @@ func New(c *Config) *Master {
|
||||
bindingRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
|
||||
eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())),
|
||||
minionRegistry: minionRegistry,
|
||||
limitRangeRegistry: limitrange.NewEtcdRegistry(c.EtcdHelper),
|
||||
client: c.Client,
|
||||
portalNet: c.PortalNet,
|
||||
rootWebService: new(restful.WebService),
|
||||
@ -361,6 +364,8 @@ func (m *Master) init(c *Config) {
|
||||
|
||||
// TODO: should appear only in scheduler API group.
|
||||
"bindings": binding.NewREST(m.bindingRegistry),
|
||||
|
||||
"limitRanges": limitrange.NewREST(m.limitRangeRegistry),
|
||||
}
|
||||
|
||||
apiVersions := []string{"v1beta1", "v1beta2"}
|
||||
|
19
pkg/registry/limitrange/doc.go
Normal file
19
pkg/registry/limitrange/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 limitrange provides Registry interface and it's REST
|
||||
// implementation for storing LimitRange api objects.
|
||||
package limitrange
|
48
pkg/registry/limitrange/registry.go
Normal file
48
pkg/registry/limitrange/registry.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 limitrange
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
)
|
||||
|
||||
// registry implements custom changes to generic.Etcd.
|
||||
type registry struct {
|
||||
*etcdgeneric.Etcd
|
||||
}
|
||||
|
||||
// NewEtcdRegistry returns a registry which will store LimitRange in the given helper
|
||||
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
|
||||
return registry{
|
||||
Etcd: &etcdgeneric.Etcd{
|
||||
NewFunc: func() runtime.Object { return &api.LimitRange{} },
|
||||
NewListFunc: func() runtime.Object { return &api.LimitRangeList{} },
|
||||
EndpointName: "limitranges",
|
||||
KeyRootFunc: func(ctx api.Context) string {
|
||||
return etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/limitranges")
|
||||
},
|
||||
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
||||
return etcdgeneric.NamespaceKeyFunc(ctx, "/registry/limitranges", id)
|
||||
},
|
||||
Helper: h,
|
||||
},
|
||||
}
|
||||
}
|
121
pkg/registry/limitrange/registry_test.go
Normal file
121
pkg/registry/limitrange/registry_test.go
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 limitrange
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
||||
"github.com/coreos/go-etcd/etcd"
|
||||
)
|
||||
|
||||
func NewTestLimitRangeEtcdRegistry(t *testing.T) (*tools.FakeEtcdClient, generic.Registry) {
|
||||
f := tools.NewFakeEtcdClient(t)
|
||||
f.TestIndex = true
|
||||
h := tools.EtcdHelper{f, testapi.Codec(), tools.RuntimeVersionAdapter{testapi.MetadataAccessor()}}
|
||||
return f, NewEtcdRegistry(h)
|
||||
}
|
||||
|
||||
func TestLimitRangeCreate(t *testing.T) {
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("0"),
|
||||
api.ResourceMemory: resource.MustParse("100"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nodeWithLimitRange := tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Value: runtime.EncodeOrDie(testapi.Codec(), limitRange),
|
||||
ModifiedIndex: 1,
|
||||
CreatedIndex: 1,
|
||||
},
|
||||
},
|
||||
E: nil,
|
||||
}
|
||||
|
||||
emptyNode := tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{},
|
||||
E: tools.EtcdErrorNotFound,
|
||||
}
|
||||
|
||||
ctx := api.NewDefaultContext()
|
||||
key := "foo"
|
||||
path, err := etcdgeneric.NamespaceKeyFunc(ctx, "/registry/limitranges", key)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
table := map[string]struct {
|
||||
existing tools.EtcdResponseWithError
|
||||
expect tools.EtcdResponseWithError
|
||||
toCreate runtime.Object
|
||||
errOK func(error) bool
|
||||
}{
|
||||
"normal": {
|
||||
existing: emptyNode,
|
||||
expect: nodeWithLimitRange,
|
||||
toCreate: limitRange,
|
||||
errOK: func(err error) bool { return err == nil },
|
||||
},
|
||||
"preExisting": {
|
||||
existing: nodeWithLimitRange,
|
||||
expect: nodeWithLimitRange,
|
||||
toCreate: limitRange,
|
||||
errOK: errors.IsAlreadyExists,
|
||||
},
|
||||
}
|
||||
|
||||
for name, item := range table {
|
||||
fakeClient, registry := NewTestLimitRangeEtcdRegistry(t)
|
||||
fakeClient.Data[path] = item.existing
|
||||
err := registry.Create(ctx, key, item.toCreate)
|
||||
if !item.errOK(err) {
|
||||
t.Errorf("%v: unexpected error: %v", name, err)
|
||||
}
|
||||
|
||||
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
}
|
159
pkg/registry/limitrange/rest.go
Normal file
159
pkg/registry/limitrange/rest.go
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 limitrange
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// REST provides the RESTStorage access patterns to work with LimitRange objects.
|
||||
type REST struct {
|
||||
registry generic.Registry
|
||||
}
|
||||
|
||||
// NewREST returns a new REST. You must use a registry created by
|
||||
// NewEtcdRegistry unless you're testing.
|
||||
func NewREST(registry generic.Registry) *REST {
|
||||
return &REST{
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a LimitRange object
|
||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||
limitRange, ok := obj.(*api.LimitRange)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
|
||||
if !api.ValidNamespace(ctx, &limitRange.ObjectMeta) {
|
||||
return nil, errors.NewConflict("limitRange", limitRange.Namespace, fmt.Errorf("LimitRange.Namespace does not match the provided context"))
|
||||
}
|
||||
|
||||
if len(limitRange.Name) == 0 {
|
||||
limitRange.Name = string(util.NewUUID())
|
||||
}
|
||||
|
||||
if errs := validation.ValidateLimitRange(limitRange); len(errs) > 0 {
|
||||
return nil, errors.NewInvalid("limitRange", limitRange.Name, errs)
|
||||
}
|
||||
api.FillObjectMetaSystemFields(ctx, &limitRange.ObjectMeta)
|
||||
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
err := rs.registry.Create(ctx, limitRange.Name, limitRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs.registry.Get(ctx, limitRange.Name)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Update updates a LimitRange object.
|
||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||
limitRange, ok := obj.(*api.LimitRange)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
|
||||
if !api.ValidNamespace(ctx, &limitRange.ObjectMeta) {
|
||||
return nil, errors.NewConflict("limitRange", limitRange.Namespace, fmt.Errorf("LimitRange.Namespace does not match the provided context"))
|
||||
}
|
||||
|
||||
oldObj, err := rs.registry.Get(ctx, limitRange.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
editLimitRange := oldObj.(*api.LimitRange)
|
||||
|
||||
// set the editable fields on the existing object
|
||||
editLimitRange.Labels = limitRange.Labels
|
||||
editLimitRange.ResourceVersion = limitRange.ResourceVersion
|
||||
editLimitRange.Annotations = limitRange.Annotations
|
||||
editLimitRange.Spec = limitRange.Spec
|
||||
|
||||
if errs := validation.ValidateLimitRange(editLimitRange); len(errs) > 0 {
|
||||
return nil, errors.NewInvalid("limitRange", editLimitRange.Name, errs)
|
||||
}
|
||||
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
err := rs.registry.Update(ctx, editLimitRange.Name, editLimitRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs.registry.Get(ctx, editLimitRange.Name)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Delete deletes the LimitRange with the specified name
|
||||
func (rs *REST) Delete(ctx api.Context, name string) (<-chan apiserver.RESTResult, error) {
|
||||
obj, err := rs.registry.Get(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ok := obj.(*api.LimitRange)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, name)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Get gets a LimitRange with the specified name
|
||||
func (rs *REST) Get(ctx api.Context, name string) (runtime.Object, error) {
|
||||
obj, err := rs.registry.Get(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limitRange, ok := obj.(*api.LimitRange)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
return limitRange, err
|
||||
}
|
||||
|
||||
func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, err error) {
|
||||
return labels.Set{}, labels.Set{}, nil
|
||||
}
|
||||
|
||||
func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
|
||||
return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
|
||||
}
|
||||
|
||||
func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
|
||||
}
|
||||
|
||||
// New returns a new api.LimitRange
|
||||
func (*REST) New() runtime.Object {
|
||||
return &api.LimitRange{}
|
||||
}
|
||||
|
||||
func (*REST) NewList() runtime.Object {
|
||||
return &api.LimitRangeList{}
|
||||
}
|
17
pkg/registry/limitrange/rest_test.go
Normal file
17
pkg/registry/limitrange/rest_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
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 limitrange
|
175
plugin/pkg/admission/limitranger/admission.go
Normal file
175
plugin/pkg/admission/limitranger/admission.go
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
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 limitranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("LimitRanger", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewLimitRanger(client, PodLimitFunc), nil
|
||||
})
|
||||
}
|
||||
|
||||
// limitRanger enforces usage limits on a per resource basis in the namespace
|
||||
type limitRanger struct {
|
||||
client client.Interface
|
||||
limitFunc LimitFunc
|
||||
}
|
||||
|
||||
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
|
||||
func (l *limitRanger) Admit(a admission.Attributes) (err error) {
|
||||
// ignore deletes
|
||||
if a.GetOperation() == "DELETE" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// look for a limit range in current namespace that requires enforcement
|
||||
// TODO: Move to cache when issue is resolved: https://github.com/GoogleCloudPlatform/kubernetes/issues/2294
|
||||
items, err := l.client.LimitRanges(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure it meets each prescribed min/max
|
||||
for i := range items.Items {
|
||||
limitRange := &items.Items[i]
|
||||
err = l.limitFunc(limitRange, a.GetKind(), a.GetObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
|
||||
func NewLimitRanger(client client.Interface, limitFunc LimitFunc) admission.Interface {
|
||||
return &limitRanger{client: client, limitFunc: limitFunc}
|
||||
}
|
||||
|
||||
func Min(a int64, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Max(a int64, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PodLimitFunc enforces that a pod spec does not exceed any limits specified on the supplied limit range
|
||||
func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) error {
|
||||
if kind != "pods" {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod := obj.(*api.Pod)
|
||||
|
||||
podCPU := int64(0)
|
||||
podMem := int64(0)
|
||||
|
||||
minContainerCPU := int64(0)
|
||||
minContainerMem := int64(0)
|
||||
maxContainerCPU := int64(0)
|
||||
maxContainerMem := int64(0)
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
container := pod.Spec.Containers[i]
|
||||
containerCPU := container.CPU.MilliValue()
|
||||
containerMem := container.Memory.Value()
|
||||
|
||||
if i == 0 {
|
||||
minContainerCPU = containerCPU
|
||||
minContainerMem = containerMem
|
||||
maxContainerCPU = containerCPU
|
||||
maxContainerMem = containerMem
|
||||
}
|
||||
|
||||
podCPU = podCPU + container.CPU.MilliValue()
|
||||
podMem = podMem + container.Memory.Value()
|
||||
|
||||
minContainerCPU = Min(containerCPU, minContainerCPU)
|
||||
minContainerMem = Min(containerMem, minContainerMem)
|
||||
maxContainerCPU = Max(containerCPU, maxContainerCPU)
|
||||
maxContainerMem = Max(containerMem, maxContainerMem)
|
||||
}
|
||||
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
for _, minOrMax := range []string{"Min", "Max"} {
|
||||
var rl api.ResourceList
|
||||
switch minOrMax {
|
||||
case "Min":
|
||||
rl = limit.Min
|
||||
case "Max":
|
||||
rl = limit.Max
|
||||
}
|
||||
for k, v := range rl {
|
||||
observed := int64(0)
|
||||
enforced := int64(0)
|
||||
var err error
|
||||
switch k {
|
||||
case api.ResourceMemory:
|
||||
enforced = v.Value()
|
||||
switch limit.Type {
|
||||
case api.LimitTypePod:
|
||||
observed = podMem
|
||||
err = fmt.Errorf("%simum memory usage per pod is %s", minOrMax, v.String())
|
||||
case api.LimitTypeContainer:
|
||||
observed = maxContainerMem
|
||||
err = fmt.Errorf("%simum memory usage per container is %s", minOrMax, v.String())
|
||||
}
|
||||
case api.ResourceCPU:
|
||||
enforced = v.MilliValue()
|
||||
switch limit.Type {
|
||||
case api.LimitTypePod:
|
||||
observed = podCPU
|
||||
err = fmt.Errorf("%simum CPU usage per pod is %s, but requested %s", minOrMax, v.String(), resource.NewMilliQuantity(observed, resource.DecimalSI))
|
||||
case api.LimitTypeContainer:
|
||||
observed = maxContainerCPU
|
||||
err = fmt.Errorf("%simum CPU usage per container is %s", minOrMax, v.String())
|
||||
}
|
||||
}
|
||||
switch minOrMax {
|
||||
case "Min":
|
||||
if observed < enforced {
|
||||
return apierrors.NewForbidden(kind, pod.Name, err)
|
||||
}
|
||||
case "Max":
|
||||
if observed > enforced {
|
||||
return apierrors.NewForbidden(kind, pod.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
238
plugin/pkg/admission/limitranger/admission_test.go
Normal file
238
plugin/pkg/admission/limitranger/admission_test.go
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
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 limitranger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
)
|
||||
|
||||
func TestPodLimitFunc(t *testing.T) {
|
||||
limitRange := &api.LimitRange{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("200m"),
|
||||
api.ResourceMemory: resource.MustParse("4Gi"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("50m"),
|
||||
api.ResourceMemory: resource.MustParse("2Mi"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: api.LimitTypeContainer,
|
||||
Max: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100m"),
|
||||
api.ResourceMemory: resource.MustParse("2Gi"),
|
||||
},
|
||||
Min: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("25m"),
|
||||
api.ResourceMemory: resource.MustParse("1Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
successCases := []api.Pod{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "foo:V1",
|
||||
CPU: resource.MustParse("100m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("100m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("100m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errorCases := map[string]api.Pod{
|
||||
"min-container-cpu": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("25m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"max-container-cpu": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("110m"),
|
||||
Memory: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"min-container-mem": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("30m"),
|
||||
Memory: resource.MustParse("0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"max-container-mem": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("30m"),
|
||||
Memory: resource.MustParse("3Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"min-pod-cpu": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("40m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"max-pod-cpu": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("1Mi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V2",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("1Mi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V3",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("1Mi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V4",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("1Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"max-pod-memory": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V2",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V3",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"min-pod-memory": {
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "boo:V1",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("0"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V2",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("0"),
|
||||
},
|
||||
{
|
||||
Image: "boo:V3",
|
||||
CPU: resource.MustParse("60m"),
|
||||
Memory: resource.MustParse("0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range successCases {
|
||||
err := PodLimitFunc(limitRange, "pods", &successCases[i])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for valid pod: %v, %v", successCases[i].Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
err := PodLimitFunc(limitRange, "pods", &v)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for %s", k)
|
||||
}
|
||||
}
|
||||
}
|
25
plugin/pkg/admission/limitranger/interfaces.go
Normal file
25
plugin/pkg/admission/limitranger/interfaces.go
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 limitranger
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// LimitFunc is a pluggable function to enforce limits on the object
|
||||
type LimitFunc func(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
Loading…
Reference in New Issue
Block a user