diff --git a/pkg/api/register.go b/pkg/api/register.go index 5c138425b18..15fbb1f2809 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -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() {} diff --git a/pkg/api/types.go b/pkg/api/types.go index a800631a8cd..84430119058 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1138,3 +1138,37 @@ type List struct { Items []runtime.Object `json:"items"` } + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind +type LimitRangeItem struct { + // Kind is the resource kind that this limit range is applied (i.e. pods, etc.) + Kind string + // 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"` +} diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 52600138907..1e539c243e5 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -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.Kind = in.Kind + 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.Kind = in.Kind + 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 { diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index 9d5e19bc8e8..992f2ed733f 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -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() {} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index ecdbf672db8..814e3f2bc19 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -904,3 +904,35 @@ type List struct { TypeMeta `json:",inline"` Items []runtime.RawExtension `json:"items" description:"list of objects"` } + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind +type LimitRangeItem struct { + // Kind is the resource kind that this limit range is applied (i.e. pods, etc.) + Kind string + // 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"` +} diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index 6862bae8709..2cd037ea03d 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -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.Kind = in.Kind + 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.Kind = in.Kind + 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 { diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index 7682d9ad246..2990dbbf739 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -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() {} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index a386857bb54..0e613c89e38 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -906,3 +906,35 @@ type List struct { TypeMeta `json:",inline"` Items []runtime.RawExtension `json:"items" description:"list of objects"` } + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind +type LimitRangeItem struct { + // Kind is the resource kind that this limit range is applied (i.e. pods, etc.) + Kind string + // 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"` +} diff --git a/pkg/api/v1beta3/register.go b/pkg/api/v1beta3/register.go index 6594cb67e89..75f2c04e81c 100644 --- a/pkg/api/v1beta3/register.go +++ b/pkg/api/v1beta3/register.go @@ -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() {} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 2947e2b1642..a14b9318b83 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -1066,3 +1066,37 @@ type List struct { Items []runtime.RawExtension `json:"items" description:"list of objects"` } + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind +type LimitRangeItem struct { + // Kind is the resource kind that this limit range is applied (i.e. pods, etc.) + Kind string + // 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"` +} diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 8cdaaedfa78..28190447ca3 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -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(k)) + } + for k, _ := range limit.Min { + allErrs = append(allErrs, ValidateResourceName(k)) + } + } + return allErrs +} diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 52fa51997e1..5dd94873e3d 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -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{ + { + Kind: "pods", + 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{ + { + Kind: "pods", + 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{ + { + Kind: "pods", + 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]) + } + } + } +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 8a92fc6ce3f..67d7258cc4b 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -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) diff --git a/pkg/client/fake.go b/pkg/client/fake.go index 7cb4c5d059b..dfa0f8d9f1d 100644 --- a/pkg/client/fake.go +++ b/pkg/client/fake.go @@ -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 { diff --git a/pkg/client/fake_limit_ranges.go b/pkg/client/fake_limit_ranges.go new file mode 100644 index 00000000000..9d1c7c237c5 --- /dev/null +++ b/pkg/client/fake_limit_ranges.go @@ -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 +} diff --git a/pkg/client/limit_ranges.go b/pkg/client/limit_ranges.go new file mode 100644 index 00000000000..fa03c61178e --- /dev/null +++ b/pkg/client/limit_ranges.go @@ -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 +} diff --git a/pkg/client/limit_ranges_test.go b/pkg/client/limit_ranges_test.go new file mode 100644 index 00000000000..e8bc09561c7 --- /dev/null +++ b/pkg/client/limit_ranges_test.go @@ -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{ + { + Kind: "pods", + 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{ + { + Kind: "pods", + 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{ + { + Kind: "pods", + 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{ + { + Kind: "pods", + 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) +} diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 3ebb5ca9ccb..7896e5dc76d 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -47,10 +47,66 @@ 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, "Kind\tResource\tMin\tMax\n") + fmt.Fprintf(out, "----\t--------\t---\t---\n") + for i, _ := range limitRange.Spec.Limits { + item := limitRange.Spec.Limits[i] + kind := item.Kind + 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, kind, k, minValue, maxValue) + } + } + return nil + }) +} + // PodDescriber generates information about a pod and the replication controllers that // create it. type PodDescriber struct { diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index 07530b4ef25..72b5c0d8f35 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -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 diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 79321044a55..73d0464e63e 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -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) diff --git a/pkg/master/master.go b/pkg/master/master.go index be5ad949b41..765c43249a6 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -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"} diff --git a/pkg/registry/limitrange/doc.go b/pkg/registry/limitrange/doc.go new file mode 100644 index 00000000000..7619321aca3 --- /dev/null +++ b/pkg/registry/limitrange/doc.go @@ -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 diff --git a/pkg/registry/limitrange/registry.go b/pkg/registry/limitrange/registry.go new file mode 100644 index 00000000000..e08d2b0d8b6 --- /dev/null +++ b/pkg/registry/limitrange/registry.go @@ -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, + }, + } +} diff --git a/pkg/registry/limitrange/registry_test.go b/pkg/registry/limitrange/registry_test.go new file mode 100644 index 00000000000..1eb649b0111 --- /dev/null +++ b/pkg/registry/limitrange/registry_test.go @@ -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{ + { + Kind: "pods", + 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)) + } + } +} diff --git a/pkg/registry/limitrange/rest.go b/pkg/registry/limitrange/rest.go new file mode 100644 index 00000000000..f5eaaa1a71d --- /dev/null +++ b/pkg/registry/limitrange/rest.go @@ -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{} +} diff --git a/pkg/registry/limitrange/rest_test.go b/pkg/registry/limitrange/rest_test.go new file mode 100644 index 00000000000..f003a69184a --- /dev/null +++ b/pkg/registry/limitrange/rest_test.go @@ -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