mirror of https://github.com/k3s-io/k3s
Merge pull request #5460 from derekwaynecarr/eliminate_resource_quota_usage
Eliminate resource quota usagepull/6/head
commit
e214802e45
|
@ -50,7 +50,6 @@ func init() {
|
|||
&LimitRangeList{},
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
|
@ -86,7 +85,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
|||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
|
|
|
@ -1390,16 +1390,6 @@ type ResourceQuota struct {
|
|||
Status ResourceQuotaStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceQuotaUsage captures system observed quota status per namespace
|
||||
// It is used to enforce atomic updates of a backing ResourceQuota.Status field in storage
|
||||
type ResourceQuotaUsage struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Status defines the actual enforced quota and its current usage
|
||||
Status ResourceQuotaStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceQuotaList is a list of ResourceQuota items
|
||||
type ResourceQuotaList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
|
|
@ -848,6 +848,9 @@ func init() {
|
|||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *ResourceQuota, out *newer.ResourceQuota, s conversion.Scope) error {
|
||||
|
@ -863,29 +866,7 @@ func init() {
|
|||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
func(in *newer.ResourceQuotaUsage, out *ResourceQuotaUsage, 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.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *ResourceQuotaUsage, out *newer.ResourceQuotaUsage, 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.Status, &out.Status, 0); err != nil {
|
||||
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -57,7 +57,6 @@ func init() {
|
|||
&LimitRangeList{},
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
|
@ -93,7 +92,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
|||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
|
|
|
@ -1159,6 +1159,7 @@ type ResourceQuotaStatus struct {
|
|||
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
||||
type ResourceQuota struct {
|
||||
TypeMeta `json:",inline"`
|
||||
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize resource quotas"`
|
||||
|
||||
// Spec defines the desired quota
|
||||
Spec ResourceQuotaSpec `json:"spec,omitempty" description:"spec defines the desired quota"`
|
||||
|
@ -1167,15 +1168,6 @@ type ResourceQuota struct {
|
|||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage"`
|
||||
}
|
||||
|
||||
// ResourceQuotaUsage captures system observed quota status per namespace
|
||||
// It is used to enforce atomic updates of a backing ResourceQuota.Status field in storage
|
||||
type ResourceQuotaUsage struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Status defines the actual enforced quota and its current usage
|
||||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage"`
|
||||
}
|
||||
|
||||
// ResourceQuotaList is a list of ResourceQuota items
|
||||
type ResourceQuotaList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
|
|
@ -768,6 +768,9 @@ func init() {
|
|||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *ResourceQuota, out *newer.ResourceQuota, s conversion.Scope) error {
|
||||
|
@ -783,29 +786,7 @@ func init() {
|
|||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
func(in *newer.ResourceQuotaUsage, out *ResourceQuotaUsage, 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.Status, &out.Status, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *ResourceQuotaUsage, out *newer.ResourceQuotaUsage, 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.Status, &out.Status, 0); err != nil {
|
||||
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -57,7 +57,6 @@ func init() {
|
|||
&LimitRangeList{},
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
|
@ -93,7 +92,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
|||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
|
|
|
@ -1221,7 +1221,7 @@ type ResourceQuotaStatus struct {
|
|||
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
||||
type ResourceQuota struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize resource quotas"`
|
||||
// Spec defines the desired quota
|
||||
Spec ResourceQuotaSpec `json:"spec,omitempty" description:"spec defines the desired quota"`
|
||||
|
||||
|
@ -1229,15 +1229,6 @@ type ResourceQuota struct {
|
|||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage"`
|
||||
}
|
||||
|
||||
// ResourceQuotaUsage captures system observed quota status per namespace
|
||||
// It is used to enforce atomic updates of a backing ResourceQuota.Status field in storage
|
||||
type ResourceQuotaUsage struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Status defines the actual enforced quota and its current usage
|
||||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage"`
|
||||
}
|
||||
|
||||
// ResourceQuotaList is a list of ResourceQuota items
|
||||
type ResourceQuotaList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
|
|
@ -51,7 +51,6 @@ func init() {
|
|||
&LimitRangeList{},
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
|
@ -87,7 +86,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
|||
func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
|
|
|
@ -1308,16 +1308,6 @@ type ResourceQuota struct {
|
|||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"`
|
||||
}
|
||||
|
||||
// ResourceQuotaUsage captures system observed quota status per namespace
|
||||
// It is used to enforce atomic updates of a backing ResourceQuota.Status field in storage
|
||||
type ResourceQuotaUsage struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"`
|
||||
|
||||
// Status defines the actual enforced quota and its current usage
|
||||
Status ResourceQuotaStatus `json:"status,omitempty" description:"status defines the actual enforced quota and current usage; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"`
|
||||
}
|
||||
|
||||
// ResourceQuotaList is a list of ResourceQuota items
|
||||
type ResourceQuotaList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
|
|
@ -972,6 +972,36 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
|||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make.
|
||||
// newResourceQuota is updated with fields that cannot be changed.
|
||||
func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *api.ResourceQuota) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldResourceQuota.ObjectMeta, &newResourceQuota.ObjectMeta).Prefix("metadata")...)
|
||||
for k := range newResourceQuota.Spec.Hard {
|
||||
allErrs = append(allErrs, validateResourceName(string(k), string(newResourceQuota.TypeMeta.Kind))...)
|
||||
}
|
||||
newResourceQuota.Status = oldResourceQuota.Status
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateResourceQuotaStatusUpdate tests to see if the status update is legal for an end user to make.
|
||||
// newResourceQuota is updated with fields that cannot be changed.
|
||||
func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *api.ResourceQuota) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldResourceQuota.ObjectMeta, &newResourceQuota.ObjectMeta).Prefix("metadata")...)
|
||||
if newResourceQuota.ResourceVersion == "" {
|
||||
allErrs = append(allErrs, fmt.Errorf("ResourceVersion must be specified"))
|
||||
}
|
||||
for k := range newResourceQuota.Status.Hard {
|
||||
allErrs = append(allErrs, validateResourceName(string(k), string(newResourceQuota.TypeMeta.Kind))...)
|
||||
}
|
||||
for k := range newResourceQuota.Status.Used {
|
||||
allErrs = append(allErrs, validateResourceName(string(k), string(newResourceQuota.TypeMeta.Kind))...)
|
||||
}
|
||||
newResourceQuota.Spec = oldResourceQuota.Spec
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateNamespace tests if required fields are set.
|
||||
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
|
|
|
@ -39,7 +39,6 @@ type Interface interface {
|
|||
EventNamespacer
|
||||
LimitRangesNamespacer
|
||||
ResourceQuotasNamespacer
|
||||
ResourceQuotaUsagesNamespacer
|
||||
SecretsNamespacer
|
||||
NamespacesInterface
|
||||
}
|
||||
|
@ -76,10 +75,6 @@ func (c *Client) ResourceQuotas(namespace string) ResourceQuotaInterface {
|
|||
return newResourceQuotas(c, namespace)
|
||||
}
|
||||
|
||||
func (c *Client) ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterface {
|
||||
return newResourceQuotaUsages(c, namespace)
|
||||
}
|
||||
|
||||
func (c *Client) Secrets(namespace string) SecretsInterface {
|
||||
return newSecrets(c, namespace)
|
||||
}
|
||||
|
|
|
@ -34,22 +34,22 @@ 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
|
||||
CtrlList api.ReplicationControllerList
|
||||
Ctrl api.ReplicationController
|
||||
ServiceList api.ServiceList
|
||||
EndpointsList api.EndpointsList
|
||||
MinionsList api.NodeList
|
||||
EventsList api.EventList
|
||||
LimitRangesList api.LimitRangeList
|
||||
ResourceQuotasList api.ResourceQuotaList
|
||||
ResourceQuotaUsage api.ResourceQuotaUsage
|
||||
NamespacesList api.NamespaceList
|
||||
SecretList api.SecretList
|
||||
Secret api.Secret
|
||||
Err error
|
||||
Watch watch.Interface
|
||||
Actions []FakeAction
|
||||
PodsList api.PodList
|
||||
CtrlList api.ReplicationControllerList
|
||||
Ctrl api.ReplicationController
|
||||
ServiceList api.ServiceList
|
||||
EndpointsList api.EndpointsList
|
||||
MinionsList api.NodeList
|
||||
EventsList api.EventList
|
||||
LimitRangesList api.LimitRangeList
|
||||
ResourceQuotaStatus api.ResourceQuota
|
||||
ResourceQuotasList api.ResourceQuotaList
|
||||
NamespacesList api.NamespaceList
|
||||
SecretList api.SecretList
|
||||
Secret api.Secret
|
||||
Err error
|
||||
Watch watch.Interface
|
||||
}
|
||||
|
||||
func (c *Fake) LimitRanges(namespace string) LimitRangeInterface {
|
||||
|
@ -60,10 +60,6 @@ func (c *Fake) ResourceQuotas(namespace string) ResourceQuotaInterface {
|
|||
return &FakeResourceQuotas{Fake: c, Namespace: namespace}
|
||||
}
|
||||
|
||||
func (c *Fake) ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterface {
|
||||
return &FakeResourceQuotaUsages{Fake: c, Namespace: namespace}
|
||||
}
|
||||
|
||||
func (c *Fake) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||
return &FakeReplicationControllers{Fake: c, Namespace: namespace}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// FakeResourceQuotaUsages implements ResourceQuotaUsageInterface. 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 FakeResourceQuotaUsages struct {
|
||||
Fake *Fake
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func (c *FakeResourceQuotaUsages) Create(resourceQuotaUsage *api.ResourceQuotaUsage) error {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "create-resourceQuotaUsage"})
|
||||
c.Fake.ResourceQuotaUsage = *resourceQuotaUsage
|
||||
return nil
|
||||
}
|
|
@ -54,6 +54,12 @@ func (c *FakeResourceQuotas) Update(resourceQuota *api.ResourceQuota) (*api.Reso
|
|||
return &api.ResourceQuota{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeResourceQuotas) Status(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-status-resourceQuota", Value: resourceQuota.Name})
|
||||
c.Fake.ResourceQuotaStatus = *resourceQuota
|
||||
return &api.ResourceQuota{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeResourceQuotas) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-resourceQuota", Value: resourceVersion})
|
||||
return c.Fake.Watch, nil
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// ResourceQuotaUsagesNamespacer has methods to work with ResourceQuotaUsage resources in a namespace
|
||||
type ResourceQuotaUsagesNamespacer interface {
|
||||
ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterface
|
||||
}
|
||||
|
||||
// ResourceQuotaUsageInterface has methods to work with ResourceQuotaUsage resources.
|
||||
type ResourceQuotaUsageInterface interface {
|
||||
Create(resourceQuotaUsage *api.ResourceQuotaUsage) error
|
||||
}
|
||||
|
||||
// resourceQuotaUsages implements ResourceQuotaUsagesNamespacer interface
|
||||
type resourceQuotaUsages struct {
|
||||
r *Client
|
||||
ns string
|
||||
}
|
||||
|
||||
// newResourceQuotaUsages returns a resourceQuotaUsages
|
||||
func newResourceQuotaUsages(c *Client, namespace string) *resourceQuotaUsages {
|
||||
return &resourceQuotaUsages{
|
||||
r: c,
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Create takes the representation of a resourceQuotaUsage. Returns an error if the usage was not applied
|
||||
func (c *resourceQuotaUsages) Create(resourceQuotaUsage *api.ResourceQuotaUsage) (err error) {
|
||||
if len(resourceQuotaUsage.ResourceVersion) == 0 {
|
||||
err = fmt.Errorf("invalid update object, missing resource version: %v", resourceQuotaUsage)
|
||||
return
|
||||
}
|
||||
err = c.r.Post().Namespace(c.ns).Resource("resourceQuotaUsages").Body(resourceQuotaUsage).Do().Error()
|
||||
return
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
func TestResourceQuotaUsageCreate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
resourceQuotaUsage := &api.ResourceQuotaUsage{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("10"),
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "POST",
|
||||
Path: buildResourcePath(ns, "/resourceQuotaUsages"),
|
||||
Query: buildQueryValues(ns, nil),
|
||||
Body: resourceQuotaUsage,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: resourceQuotaUsage},
|
||||
}
|
||||
|
||||
err := c.Setup().ResourceQuotaUsages(ns).Create(resourceQuotaUsage)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidResourceQuotaUsageCreate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
resourceQuotaUsage := &api.ResourceQuotaUsage{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("10"),
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "POST",
|
||||
Path: buildResourcePath(ns, "/resourceQuotaUsages"),
|
||||
Query: buildQueryValues(ns, nil),
|
||||
Body: resourceQuotaUsage,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: resourceQuotaUsage},
|
||||
}
|
||||
|
||||
err := c.Setup().ResourceQuotaUsages(ns).Create(resourceQuotaUsage)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error due to missing ResourceVersion")
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ type ResourceQuotaInterface interface {
|
|||
Delete(name string) error
|
||||
Create(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error)
|
||||
Update(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error)
|
||||
Status(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error)
|
||||
Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error)
|
||||
}
|
||||
|
||||
|
@ -84,7 +85,7 @@ func (c *resourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.R
|
|||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a resourceQuota to update. Returns the server's representation of the resourceQuota, and an error, if it occurs.
|
||||
// Update takes the representation of a resourceQuota to update spec. Returns the server's representation of the resourceQuota, and an error, if it occurs.
|
||||
func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) {
|
||||
result = &api.ResourceQuota{}
|
||||
if len(resourceQuota.ResourceVersion) == 0 {
|
||||
|
@ -95,6 +96,17 @@ func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.R
|
|||
return
|
||||
}
|
||||
|
||||
// Status takes the representation of a resourceQuota to update status. Returns the server's representation of the resourceQuota, and an error, if it occurs.
|
||||
func (c *resourceQuotas) Status(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) {
|
||||
result = &api.ResourceQuota{}
|
||||
if len(resourceQuota.ResourceVersion) == 0 {
|
||||
err = fmt.Errorf("invalid update object, missing resource version: %v", resourceQuota)
|
||||
return
|
||||
}
|
||||
err = c.r.Put().Namespace(c.ns).Resource("resourceQuotas").Name(resourceQuota.Name).SubResource("status").Body(resourceQuota).Do().Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested resource
|
||||
func (c *resourceQuotas) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return c.r.Get().
|
||||
|
|
|
@ -139,6 +139,33 @@ func TestResourceQuotaUpdate(t *testing.T) {
|
|||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestResourceQuotaStatusUpdate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
resourceQuota := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("10"),
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/resourceQuotas/abc/status"), Query: buildQueryValues(ns, nil)},
|
||||
Response: Response{StatusCode: 200, Body: resourceQuota},
|
||||
}
|
||||
response, err := c.Setup().ResourceQuotas(ns).Status(resourceQuota)
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestInvalidResourceQuotaUpdate(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
resourceQuota := &api.ResourceQuota{
|
||||
|
|
|
@ -51,8 +51,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
||||
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
|
@ -377,7 +376,8 @@ func (m *Master) init(c *Config) {
|
|||
|
||||
eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds()))
|
||||
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
|
||||
resourceQuotaRegistry := resourcequota.NewEtcdRegistry(c.EtcdHelper)
|
||||
|
||||
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(c.EtcdHelper)
|
||||
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
|
||||
m.namespaceRegistry = namespace.NewEtcdRegistry(c.EtcdHelper)
|
||||
|
||||
|
@ -418,11 +418,11 @@ func (m *Master) init(c *Config) {
|
|||
"nodes": nodeStorage,
|
||||
"events": event.NewREST(eventRegistry),
|
||||
|
||||
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
||||
"resourceQuotas": resourcequota.NewREST(resourceQuotaRegistry),
|
||||
"resourceQuotaUsages": resourcequotausage.NewREST(resourceQuotaRegistry),
|
||||
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
||||
"secrets": secret.NewREST(secretRegistry),
|
||||
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
||||
"resourceQuotas": resourceQuotaStorage,
|
||||
"resourceQuotas/status": resourceQuotaStatusStorage,
|
||||
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
||||
"secrets": secret.NewREST(secretRegistry),
|
||||
}
|
||||
|
||||
apiVersions := []string{"v1beta1", "v1beta2"}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright 2015 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// rest implements a RESTStorage for resourcequotas against etcd
|
||||
type REST struct {
|
||||
store *etcdgeneric.Etcd
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against ResourceQuota objects.
|
||||
func NewREST(h tools.EtcdHelper) (*REST, *StatusREST) {
|
||||
prefix := "/registry/resourcequotas"
|
||||
store := &etcdgeneric.Etcd{
|
||||
NewFunc: func() runtime.Object { return &api.ResourceQuota{} },
|
||||
NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} },
|
||||
KeyRootFunc: func(ctx api.Context) string {
|
||||
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
|
||||
},
|
||||
KeyFunc: func(ctx api.Context, name string) (string, error) {
|
||||
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name)
|
||||
},
|
||||
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
||||
return obj.(*api.ResourceQuota).Name, nil
|
||||
},
|
||||
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
|
||||
return resourcequota.MatchResourceQuota(label, field)
|
||||
},
|
||||
EndpointName: "resourcequotas",
|
||||
|
||||
Helper: h,
|
||||
}
|
||||
|
||||
store.CreateStrategy = resourcequota.Strategy
|
||||
store.UpdateStrategy = resourcequota.Strategy
|
||||
store.ReturnDeletedObject = true
|
||||
|
||||
statusStore := *store
|
||||
statusStore.UpdateStrategy = resourcequota.StatusStrategy
|
||||
|
||||
return &REST{store: store}, &StatusREST{store: &statusStore}
|
||||
}
|
||||
|
||||
// New returns a new object
|
||||
func (r *REST) New() runtime.Object {
|
||||
return r.store.NewFunc()
|
||||
}
|
||||
|
||||
// NewList returns a new list object
|
||||
func (r *REST) NewList() runtime.Object {
|
||||
return r.store.NewListFunc()
|
||||
}
|
||||
|
||||
// List obtains a list of resourcequotas with labels that match selector.
|
||||
func (r *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
|
||||
return r.store.List(ctx, label, field)
|
||||
}
|
||||
|
||||
// Watch begins watching for new, changed, or deleted resourcequotas.
|
||||
func (r *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return r.store.Watch(ctx, label, field, resourceVersion)
|
||||
}
|
||||
|
||||
// Get gets a specific resourcequota specified by its ID.
|
||||
func (r *REST) Get(ctx api.Context, name string) (runtime.Object, error) {
|
||||
return r.store.Get(ctx, name)
|
||||
}
|
||||
|
||||
// Create creates a resourcequota based on a specification.
|
||||
func (r *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
return r.store.Create(ctx, obj)
|
||||
}
|
||||
|
||||
// Update changes a resourcequota specification.
|
||||
func (r *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||
return r.store.Update(ctx, obj)
|
||||
}
|
||||
|
||||
// Delete deletes an existing resourcequota specified by its name.
|
||||
func (r *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
|
||||
return r.store.Delete(ctx, name)
|
||||
}
|
||||
|
||||
// StatusREST implements the REST endpoint for changing the status of a resourcequota.
|
||||
type StatusREST struct {
|
||||
store *etcdgeneric.Etcd
|
||||
}
|
||||
|
||||
func (r *StatusREST) New() runtime.Object {
|
||||
return &api.ResourceQuota{}
|
||||
}
|
||||
|
||||
// Update alters the status subset of an object.
|
||||
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||
return r.store.Update(ctx, obj)
|
||||
}
|
|
@ -0,0 +1,678 @@
|
|||
/*
|
||||
Copyright 2015 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
||||
"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 newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
|
||||
fakeEtcdClient := tools.NewFakeEtcdClient(t)
|
||||
fakeEtcdClient.TestIndex = true
|
||||
helper := tools.EtcdHelper{Client: fakeEtcdClient, Codec: latest.Codec, ResourceVersioner: tools.RuntimeVersionAdapter{latest.ResourceVersioner}}
|
||||
return fakeEtcdClient, helper
|
||||
}
|
||||
|
||||
func newStorage(t *testing.T) (*REST, *StatusREST, *tools.FakeEtcdClient, tools.EtcdHelper) {
|
||||
fakeEtcdClient, h := newHelper(t)
|
||||
storage, statusStorage := NewREST(h)
|
||||
return storage, statusStorage, fakeEtcdClient, h
|
||||
}
|
||||
|
||||
func validNewResourceQuota() *api.ResourceQuota {
|
||||
return &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("4Gi"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validChangedResourceQuota() *api.ResourceQuota {
|
||||
resourcequota := validNewResourceQuota()
|
||||
resourcequota.ResourceVersion = "1"
|
||||
resourcequota.Labels = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
return resourcequota
|
||||
}
|
||||
|
||||
func TestStorage(t *testing.T) {
|
||||
storage, _, _, _ := newStorage(t)
|
||||
resourcequota.NewRegistry(storage)
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
storage, _ := NewREST(helper)
|
||||
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
||||
resourcequota := validNewResourceQuota()
|
||||
resourcequota.ObjectMeta = api.ObjectMeta{}
|
||||
test.TestCreate(
|
||||
// valid
|
||||
resourcequota,
|
||||
// invalid
|
||||
&api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func expectResourceQuota(t *testing.T, out runtime.Object) (*api.ResourceQuota, bool) {
|
||||
resourcequota, ok := out.(*api.ResourceQuota)
|
||||
if !ok || resourcequota == nil {
|
||||
t.Errorf("Expected an api.ResourceQuota object, was %#v", out)
|
||||
return nil, false
|
||||
}
|
||||
return resourcequota, true
|
||||
}
|
||||
|
||||
func TestCreateRegistryError(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Err = fmt.Errorf("test error")
|
||||
storage, _ := NewREST(helper)
|
||||
|
||||
resourcequota := validNewResourceQuota()
|
||||
_, err := storage.Create(api.NewDefaultContext(), resourcequota)
|
||||
if err != fakeEtcdClient.Err {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSetsFields(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
storage, _ := NewREST(helper)
|
||||
resourcequota := validNewResourceQuota()
|
||||
_, err := storage.Create(api.NewDefaultContext(), resourcequota)
|
||||
if err != fakeEtcdClient.Err {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
actual := &api.ResourceQuota{}
|
||||
if err := helper.ExtractObj("/registry/resourcequotas/default/foo", actual, false); err != nil {
|
||||
t.Fatalf("unexpected extraction error: %v", err)
|
||||
}
|
||||
if actual.Name != resourcequota.Name {
|
||||
t.Errorf("unexpected resourcequota: %#v", actual)
|
||||
}
|
||||
if len(actual.UID) == 0 {
|
||||
t.Errorf("expected resourcequota UID to be set: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListError(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Err = fmt.Errorf("test error")
|
||||
storage, _ := NewREST(helper)
|
||||
resourcequotas, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
|
||||
if err != fakeEtcdClient.Err {
|
||||
t.Fatalf("Expected %#v, Got %#v", fakeEtcdClient.Err, err)
|
||||
}
|
||||
if resourcequotas != nil {
|
||||
t.Errorf("Unexpected non-nil resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEmptyResourceQuotaList(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.ChangeIndex = 1
|
||||
fakeEtcdClient.Data["/registry/resourcequotas"] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{},
|
||||
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
|
||||
}
|
||||
|
||||
storage, _ := NewREST(helper)
|
||||
resourcequotas, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(resourcequotas.(*api.ResourceQuotaList).Items) != 0 {
|
||||
t.Errorf("Unexpected non-zero resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
if resourcequotas.(*api.ResourceQuotaList).ResourceVersion != "1" {
|
||||
t.Errorf("Unexpected resource version: %#v", resourcequotas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListResourceQuotaList(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Data["/registry/resourcequotas/default"] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Nodes: []*etcd.Node{
|
||||
{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
}),
|
||||
},
|
||||
{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
storage, _ := NewREST(helper)
|
||||
resourcequotasObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
|
||||
resourcequotas := resourcequotasObj.(*api.ResourceQuotaList)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(resourcequotas.Items) != 2 {
|
||||
t.Errorf("Unexpected resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
if resourcequotas.Items[0].Name != "foo" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequotas.Items[0])
|
||||
}
|
||||
if resourcequotas.Items[1].Name != "bar" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequotas.Items[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestListResourceQuotaListSelection(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Data["/registry/resourcequotas/default"] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Nodes: []*etcd.Node{
|
||||
{Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
})},
|
||||
{Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "qux",
|
||||
Labels: map[string]string{"label": "qux"},
|
||||
},
|
||||
})},
|
||||
{Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "zot"},
|
||||
})},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
storage, _ := NewREST(helper)
|
||||
ctx := api.NewDefaultContext()
|
||||
|
||||
table := []struct {
|
||||
label, field string
|
||||
expectedIDs util.StringSet
|
||||
}{
|
||||
{
|
||||
expectedIDs: util.NewStringSet("foo", "qux", "zot"),
|
||||
}, {
|
||||
field: "name=zot",
|
||||
expectedIDs: util.NewStringSet("zot"),
|
||||
}, {
|
||||
label: "label=qux",
|
||||
expectedIDs: util.NewStringSet("qux"),
|
||||
},
|
||||
}
|
||||
|
||||
for index, item := range table {
|
||||
label, err := labels.Parse(item.label)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
field, err := fields.ParseSelector(item.field)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
resourcequotasObj, err := storage.List(ctx, label, field)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequotas := resourcequotasObj.(*api.ResourceQuotaList)
|
||||
|
||||
set := util.NewStringSet()
|
||||
for i := range resourcequotas.Items {
|
||||
set.Insert(resourcequotas.Items[i].Name)
|
||||
}
|
||||
if e, a := len(item.expectedIDs), len(set); e != a {
|
||||
t.Errorf("%v: Expected %v, got %v", index, item.expectedIDs, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceQuotaDecode(t *testing.T) {
|
||||
storage, _ := NewREST(tools.EtcdHelper{})
|
||||
expected := validNewResourceQuota()
|
||||
body, err := latest.Codec.Encode(expected)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
actual := storage.New()
|
||||
if err := latest.Codec.DecodeInto(body, actual); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !api.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("mismatch: %s", util.ObjectDiff(expected, actual))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
expect := validNewResourceQuota()
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Data["/registry/resourcequotas/test/foo"] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, expect),
|
||||
},
|
||||
},
|
||||
}
|
||||
storage, _ := NewREST(helper)
|
||||
obj, err := storage.Get(api.WithNamespace(api.NewContext(), "test"), "foo")
|
||||
resourcequota := obj.(*api.ResourceQuota)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if e, a := expect, resourcequota; !api.Semantic.DeepEqual(e, a) {
|
||||
t.Errorf("Unexpected resourcequota: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteResourceQuota(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.ChangeIndex = 1
|
||||
fakeEtcdClient.Data["/registry/resourcequotas/default/foo"] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{},
|
||||
}),
|
||||
ModifiedIndex: 1,
|
||||
CreatedIndex: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
storage, _ := NewREST(helper)
|
||||
_, err := storage.Delete(api.NewDefaultContext(), "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEtcdGetDifferentNamespace ensures same-name resourcequotas in different namespaces do not clash
|
||||
func TestEtcdGetDifferentNamespace(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
|
||||
ctx1 := api.NewDefaultContext()
|
||||
ctx2 := api.WithNamespace(api.NewContext(), "other")
|
||||
|
||||
key1, _ := registry.store.KeyFunc(ctx1, "foo")
|
||||
key2, _ := registry.store.KeyFunc(ctx2, "foo")
|
||||
|
||||
fakeClient.Set(key1, runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{ObjectMeta: api.ObjectMeta{Namespace: "default", Name: "foo"}}), 0)
|
||||
fakeClient.Set(key2, runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{ObjectMeta: api.ObjectMeta{Namespace: "other", Name: "foo"}}), 0)
|
||||
|
||||
obj, err := registry.Get(ctx1, "foo")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequota1 := obj.(*api.ResourceQuota)
|
||||
if resourcequota1.Name != "foo" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequota1)
|
||||
}
|
||||
if resourcequota1.Namespace != "default" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequota1)
|
||||
}
|
||||
|
||||
obj, err = registry.Get(ctx2, "foo")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequota2 := obj.(*api.ResourceQuota)
|
||||
if resourcequota2.Name != "foo" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequota2)
|
||||
}
|
||||
if resourcequota2.Namespace != "other" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequota2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEtcdGet(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key, _ := registry.store.KeyFunc(ctx, "foo")
|
||||
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0)
|
||||
obj, err := registry.Get(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequota := obj.(*api.ResourceQuota)
|
||||
if resourcequota.Name != "foo" {
|
||||
t.Errorf("Unexpected resourcequota: %#v", resourcequota)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdGetNotFound(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key, _ := registry.store.KeyFunc(ctx, "foo")
|
||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: nil,
|
||||
},
|
||||
E: tools.EtcdErrorNotFound,
|
||||
}
|
||||
_, err := registry.Get(ctx, "foo")
|
||||
if !errors.IsNotFound(err) {
|
||||
t.Errorf("Unexpected error returned: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdCreateFailsWithoutNamespace(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
fakeClient.TestIndex = true
|
||||
resourcequota := validNewResourceQuota()
|
||||
resourcequota.Namespace = ""
|
||||
_, err := registry.Create(api.NewContext(), resourcequota)
|
||||
// Accept "namespace" or "Namespace".
|
||||
if err == nil || !strings.Contains(err.Error(), "amespace") {
|
||||
t.Fatalf("expected error that namespace was missing from context, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdCreateAlreadyExisting(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key, _ := registry.store.KeyFunc(ctx, "foo")
|
||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
|
||||
},
|
||||
},
|
||||
E: nil,
|
||||
}
|
||||
_, err := registry.Create(ctx, validNewResourceQuota())
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
t.Errorf("Unexpected error returned: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdUpdateStatus(t *testing.T) {
|
||||
registry, status, fakeClient, helper := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
fakeClient.TestIndex = true
|
||||
|
||||
key, _ := registry.store.KeyFunc(ctx, "foo")
|
||||
resourcequotaStart := validNewResourceQuota()
|
||||
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, resourcequotaStart), 1)
|
||||
|
||||
resourcequotaIn := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Used: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("1"),
|
||||
api.ResourceMemory: resource.MustParse("1Gi"),
|
||||
api.ResourcePods: resource.MustParse("1"),
|
||||
api.ResourceServices: resource.MustParse("1"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("1"),
|
||||
api.ResourceQuotas: resource.MustParse("1"),
|
||||
},
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("4Gi"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := *resourcequotaStart
|
||||
expected.ResourceVersion = "2"
|
||||
expected.Labels = resourcequotaIn.Labels
|
||||
expected.Status = resourcequotaIn.Status
|
||||
|
||||
_, _, err := status.Update(ctx, resourcequotaIn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
var resourcequotaOut api.ResourceQuota
|
||||
if err := helper.ExtractObj(key, &resourcequotaOut, false); err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if !api.Semantic.DeepEqual(expected, resourcequotaOut) {
|
||||
t.Errorf("unexpected object: %s", util.ObjectDiff(expected, resourcequotaOut))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdEmptyList(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key := registry.store.KeyRootFunc(ctx)
|
||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Nodes: []*etcd.Node{},
|
||||
},
|
||||
},
|
||||
E: nil,
|
||||
}
|
||||
|
||||
obj, err := registry.List(ctx, labels.Everything(), fields.Everything())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequotas := obj.(*api.ResourceQuotaList)
|
||||
if len(resourcequotas.Items) != 0 {
|
||||
t.Errorf("Unexpected resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdListNotFound(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key := registry.store.KeyRootFunc(ctx)
|
||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{},
|
||||
E: tools.EtcdErrorNotFound,
|
||||
}
|
||||
obj, err := registry.List(ctx, labels.Everything(), fields.Everything())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequotas := obj.(*api.ResourceQuotaList)
|
||||
if len(resourcequotas.Items) != 0 {
|
||||
t.Errorf("Unexpected resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdList(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
key := registry.store.KeyRootFunc(ctx)
|
||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Nodes: []*etcd.Node{
|
||||
{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
}),
|
||||
},
|
||||
{
|
||||
Value: runtime.EncodeOrDie(latest.Codec, &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: nil,
|
||||
}
|
||||
obj, err := registry.List(ctx, labels.Everything(), fields.Everything())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
resourcequotas := obj.(*api.ResourceQuotaList)
|
||||
|
||||
if len(resourcequotas.Items) != 2 || resourcequotas.Items[0].Name != "foo" || resourcequotas.Items[1].Name != "bar" {
|
||||
t.Errorf("Unexpected resourcequota list: %#v", resourcequotas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdWatchResourceQuotas(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
watching, err := registry.Watch(ctx,
|
||||
labels.Everything(),
|
||||
fields.Everything(),
|
||||
"1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
fakeClient.WaitForWatchCompletion()
|
||||
|
||||
select {
|
||||
case _, ok := <-watching.ResultChan():
|
||||
if !ok {
|
||||
t.Errorf("watching channel should be open")
|
||||
}
|
||||
default:
|
||||
}
|
||||
fakeClient.WatchInjectError <- nil
|
||||
if _, ok := <-watching.ResultChan(); ok {
|
||||
t.Errorf("watching channel should be closed")
|
||||
}
|
||||
watching.Stop()
|
||||
}
|
||||
|
||||
func TestEtcdWatchResourceQuotasMatch(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
watching, err := registry.Watch(ctx,
|
||||
labels.SelectorFromSet(labels.Set{"name": "foo"}),
|
||||
fields.Everything(),
|
||||
"1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
fakeClient.WaitForWatchCompletion()
|
||||
|
||||
resourcequota := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{
|
||||
"name": "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
resourcequotaBytes, _ := latest.Codec.Encode(resourcequota)
|
||||
fakeClient.WatchResponse <- &etcd.Response{
|
||||
Action: "create",
|
||||
Node: &etcd.Node{
|
||||
Value: string(resourcequotaBytes),
|
||||
},
|
||||
}
|
||||
select {
|
||||
case _, ok := <-watching.ResultChan():
|
||||
if !ok {
|
||||
t.Errorf("watching channel should be open")
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
t.Error("unexpected timeout from result channel")
|
||||
}
|
||||
watching.Stop()
|
||||
}
|
||||
|
||||
func TestEtcdWatchResourceQuotasNotMatch(t *testing.T) {
|
||||
registry, _, fakeClient, _ := newStorage(t)
|
||||
ctx := api.NewDefaultContext()
|
||||
watching, err := registry.Watch(ctx,
|
||||
labels.SelectorFromSet(labels.Set{"name": "foo"}),
|
||||
fields.Everything(),
|
||||
"1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
fakeClient.WaitForWatchCompletion()
|
||||
|
||||
resourcequota := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "bar",
|
||||
Labels: map[string]string{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
resourcequotaBytes, _ := latest.Codec.Encode(resourcequota)
|
||||
fakeClient.WatchResponse <- &etcd.Response{
|
||||
Action: "create",
|
||||
Node: &etcd.Node{
|
||||
Value: string(resourcequotaBytes),
|
||||
},
|
||||
}
|
||||
|
||||
select {
|
||||
case <-watching.ResultChan():
|
||||
t.Error("unexpected result from result channel")
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
// expected case
|
||||
}
|
||||
}
|
|
@ -17,59 +17,84 @@ limitations under the License.
|
|||
package resourcequota
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/registry/resourcequotausage"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// Registry implements operations to modify ResourceQuota objects
|
||||
// Registry is an interface implemented by things that know how to store ResourceQuota objects.
|
||||
type Registry interface {
|
||||
generic.Registry
|
||||
resourcequotausage.Registry
|
||||
// ListResourceQuotas obtains a list of pods having labels which match selector.
|
||||
ListResourceQuotas(ctx api.Context, selector labels.Selector) (*api.ResourceQuotaList, error)
|
||||
// Watch for new/changed/deleted pods
|
||||
WatchResourceQuotas(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
|
||||
// Get a specific pod
|
||||
GetResourceQuota(ctx api.Context, podID string) (*api.ResourceQuota, error)
|
||||
// Create a pod based on a specification.
|
||||
CreateResourceQuota(ctx api.Context, pod *api.ResourceQuota) error
|
||||
// Update an existing pod
|
||||
UpdateResourceQuota(ctx api.Context, pod *api.ResourceQuota) error
|
||||
// Delete an existing pod
|
||||
DeleteResourceQuota(ctx api.Context, podID string) error
|
||||
}
|
||||
|
||||
// registry implements custom changes to generic.Etcd.
|
||||
type registry struct {
|
||||
*etcdgeneric.Etcd
|
||||
// Storage is an interface for a standard REST Storage backend
|
||||
// TODO: move me somewhere common
|
||||
type Storage interface {
|
||||
apiserver.RESTDeleter
|
||||
apiserver.RESTLister
|
||||
apiserver.RESTGetter
|
||||
apiserver.ResourceWatcher
|
||||
|
||||
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
|
||||
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// ApplyStatus atomically updates the ResourceQuotaStatus based on the observed ResourceQuotaUsage
|
||||
func (r *registry) ApplyStatus(ctx api.Context, usage *api.ResourceQuotaUsage) error {
|
||||
obj, err := r.Get(ctx, usage.Name)
|
||||
// storage puts strong typing around storage calls
|
||||
type storage struct {
|
||||
Storage
|
||||
}
|
||||
|
||||
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
|
||||
// types will panic.
|
||||
func NewRegistry(s Storage) Registry {
|
||||
return &storage{s}
|
||||
}
|
||||
|
||||
func (s *storage) ListResourceQuotas(ctx api.Context, label labels.Selector) (*api.ResourceQuotaList, error) {
|
||||
obj, err := s.List(ctx, label, fields.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(usage.ResourceVersion) == 0 {
|
||||
return fmt.Errorf("A resource observation must have a resourceVersion specified to ensure atomic updates")
|
||||
}
|
||||
|
||||
// set the status
|
||||
resourceQuota := obj.(*api.ResourceQuota)
|
||||
resourceQuota.ResourceVersion = usage.ResourceVersion
|
||||
resourceQuota.Status = usage.Status
|
||||
return r.UpdateWithName(ctx, resourceQuota.Name, resourceQuota)
|
||||
return obj.(*api.ResourceQuotaList), nil
|
||||
}
|
||||
|
||||
// NewEtcdRegistry returns a registry which will store ResourceQuota in the given helper
|
||||
func NewEtcdRegistry(h tools.EtcdHelper) Registry {
|
||||
return ®istry{
|
||||
Etcd: &etcdgeneric.Etcd{
|
||||
NewFunc: func() runtime.Object { return &api.ResourceQuota{} },
|
||||
NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} },
|
||||
EndpointName: "resourcequotas",
|
||||
KeyRootFunc: func(ctx api.Context) string {
|
||||
return etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/resourcequotas")
|
||||
},
|
||||
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
||||
return etcdgeneric.NamespaceKeyFunc(ctx, "/registry/resourcequotas", id)
|
||||
},
|
||||
Helper: h,
|
||||
},
|
||||
}
|
||||
func (s *storage) WatchResourceQuotas(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return s.Watch(ctx, label, field, resourceVersion)
|
||||
}
|
||||
|
||||
func (s *storage) GetResourceQuota(ctx api.Context, podID string) (*api.ResourceQuota, error) {
|
||||
obj, err := s.Get(ctx, podID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*api.ResourceQuota), nil
|
||||
}
|
||||
|
||||
func (s *storage) CreateResourceQuota(ctx api.Context, pod *api.ResourceQuota) error {
|
||||
_, err := s.Create(ctx, pod)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *storage) UpdateResourceQuota(ctx api.Context, pod *api.ResourceQuota) error {
|
||||
_, _, err := s.Update(ctx, pod)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *storage) DeleteResourceQuota(ctx api.Context, podID string) error {
|
||||
_, err := s.Delete(ctx, podID)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
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 resourcequota
|
||||
|
||||
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 TestResourceQuotaCreate(t *testing.T) {
|
||||
resourceQuota := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("10000"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("10"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nodeWithResourceQuota := tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{
|
||||
Node: &etcd.Node{
|
||||
Value: runtime.EncodeOrDie(testapi.Codec(), resourceQuota),
|
||||
ModifiedIndex: 1,
|
||||
CreatedIndex: 1,
|
||||
},
|
||||
},
|
||||
E: nil,
|
||||
}
|
||||
|
||||
emptyNode := tools.EtcdResponseWithError{
|
||||
R: &etcd.Response{},
|
||||
E: tools.EtcdErrorNotFound,
|
||||
}
|
||||
|
||||
ctx := api.NewDefaultContext()
|
||||
key := "abc"
|
||||
path, err := etcdgeneric.NamespaceKeyFunc(ctx, "/registry/resourcequotas", 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: nodeWithResourceQuota,
|
||||
toCreate: resourceQuota,
|
||||
errOK: func(err error) bool { return err == nil },
|
||||
},
|
||||
"preExisting": {
|
||||
existing: nodeWithResourceQuota,
|
||||
expect: nodeWithResourceQuota,
|
||||
toCreate: resourceQuota,
|
||||
errOK: errors.IsAlreadyExists,
|
||||
},
|
||||
}
|
||||
|
||||
for name, item := range table {
|
||||
fakeClient, registry := NewTestLimitRangeEtcdRegistry(t)
|
||||
fakeClient.Data[path] = item.existing
|
||||
err := registry.CreateWithName(ctx, key, item.toCreate)
|
||||
if !item.errOK(err) {
|
||||
t.Errorf("%v: unexpected error: %v, %v", name, err, path)
|
||||
}
|
||||
|
||||
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,131 +26,71 @@ import (
|
|||
"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 ResourceQuota objects.
|
||||
type REST struct {
|
||||
registry generic.Registry
|
||||
// resourcequotaStrategy implements behavior for ResourceQuota objects
|
||||
type resourcequotaStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
api.NameGenerator
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
// Strategy is the default logic that applies when creating and updating ResourceQuota
|
||||
// objects via the REST API.
|
||||
var Strategy = resourcequotaStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// NamespaceScoped is true for resourcequotas.
|
||||
func (resourcequotaStrategy) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Create a ResourceQuota object
|
||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
|
||||
if !api.ValidNamespace(ctx, &resourceQuota.ObjectMeta) {
|
||||
return nil, errors.NewConflict("resourceQuota", resourceQuota.Namespace, fmt.Errorf("ResourceQuota.Namespace does not match the provided context"))
|
||||
}
|
||||
|
||||
if len(resourceQuota.Name) == 0 {
|
||||
resourceQuota.Name = string(util.NewUUID())
|
||||
}
|
||||
|
||||
// callers are not able to set status, instead, it is supplied via a control loop
|
||||
resourceQuota.Status = api.ResourceQuotaStatus{}
|
||||
|
||||
if errs := validation.ValidateResourceQuota(resourceQuota); len(errs) > 0 {
|
||||
return nil, errors.NewInvalid("resourceQuota", resourceQuota.Name, errs)
|
||||
}
|
||||
api.FillObjectMetaSystemFields(ctx, &resourceQuota.ObjectMeta)
|
||||
|
||||
err := rs.registry.CreateWithName(ctx, resourceQuota.Name, resourceQuota)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs.registry.Get(ctx, resourceQuota.Name)
|
||||
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||
func (resourcequotaStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||
resourcequota := obj.(*api.ResourceQuota)
|
||||
resourcequota.Status = api.ResourceQuotaStatus{}
|
||||
}
|
||||
|
||||
// Update updates a ResourceQuota object.
|
||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("invalid object type")
|
||||
}
|
||||
|
||||
if !api.ValidNamespace(ctx, &resourceQuota.ObjectMeta) {
|
||||
return nil, false, errors.NewConflict("resourceQuota", resourceQuota.Namespace, fmt.Errorf("ResourceQuota.Namespace does not match the provided context"))
|
||||
}
|
||||
|
||||
oldObj, err := rs.registry.Get(ctx, resourceQuota.Name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
editResourceQuota := oldObj.(*api.ResourceQuota)
|
||||
|
||||
// set the editable fields on the existing object
|
||||
editResourceQuota.Labels = resourceQuota.Labels
|
||||
editResourceQuota.ResourceVersion = resourceQuota.ResourceVersion
|
||||
editResourceQuota.Annotations = resourceQuota.Annotations
|
||||
editResourceQuota.Spec = resourceQuota.Spec
|
||||
|
||||
if errs := validation.ValidateResourceQuota(editResourceQuota); len(errs) > 0 {
|
||||
return nil, false, errors.NewInvalid("resourceQuota", editResourceQuota.Name, errs)
|
||||
}
|
||||
|
||||
if err := rs.registry.UpdateWithName(ctx, editResourceQuota.Name, editResourceQuota); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
out, err := rs.registry.Get(ctx, editResourceQuota.Name)
|
||||
return out, false, err
|
||||
// Validate validates a new resourcequota.
|
||||
func (resourcequotaStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||
resourcequota := obj.(*api.ResourceQuota)
|
||||
return validation.ValidateResourceQuota(resourcequota)
|
||||
}
|
||||
|
||||
// Delete deletes the ResourceQuota with the specified name
|
||||
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
|
||||
obj, err := rs.registry.Get(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// AllowCreateOnUpdate is false for resourcequotas.
|
||||
func (resourcequotaStrategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user.
|
||||
func (resourcequotaStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErrorList {
|
||||
return validation.ValidateResourceQuotaUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota))
|
||||
}
|
||||
|
||||
type resourcequotaStatusStrategy struct {
|
||||
resourcequotaStrategy
|
||||
}
|
||||
|
||||
var StatusStrategy = resourcequotaStatusStrategy{Strategy}
|
||||
|
||||
func (resourcequotaStatusStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErrorList {
|
||||
return validation.ValidateResourceQuotaStatusUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota))
|
||||
}
|
||||
|
||||
// MatchResourceQuota returns a generic matcher for a given label and field selector.
|
||||
func MatchResourceQuota(label labels.Selector, field fields.Selector) generic.Matcher {
|
||||
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
|
||||
resourcequotaObj, ok := obj.(*api.ResourceQuota)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("not a resourcequota")
|
||||
}
|
||||
fields := ResourceQuotaToSelectableFields(resourcequotaObj)
|
||||
return label.Matches(labels.Set(resourcequotaObj.Labels)) && field.Matches(fields), nil
|
||||
})
|
||||
}
|
||||
|
||||
// ResourceQuotaToSelectableFields returns a label set that represents the object
|
||||
// TODO: fields are not labels, and the validation rules for them do not apply.
|
||||
func ResourceQuotaToSelectableFields(resourcequota *api.ResourceQuota) labels.Set {
|
||||
return labels.Set{
|
||||
"name": resourcequota.Name,
|
||||
}
|
||||
_, ok := obj.(*api.ResourceQuota)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
return rs.registry.Delete(ctx, name)
|
||||
}
|
||||
|
||||
// Get gets a ResourceQuota 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
|
||||
}
|
||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
return resourceQuota, err
|
||||
}
|
||||
|
||||
func (rs *REST) getAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) {
|
||||
return labels.Set{}, fields.Set{}, nil
|
||||
}
|
||||
|
||||
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
|
||||
return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
|
||||
}
|
||||
|
||||
func (rs *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
|
||||
}
|
||||
|
||||
// New returns a new api.ResourceQuota
|
||||
func (*REST) New() runtime.Object {
|
||||
return &api.ResourceQuota{}
|
||||
}
|
||||
|
||||
func (*REST) NewList() runtime.Object {
|
||||
return &api.ResourceQuotaList{}
|
||||
}
|
||||
|
|
|
@ -17,163 +17,42 @@ limitations under the License.
|
|||
package resourcequota
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func makeRegistry(resourceList runtime.Object) (*registrytest.GenericRegistry, *REST) {
|
||||
registry := registrytest.NewGeneric(resourceList)
|
||||
rest := NewREST(registry)
|
||||
return registry, rest
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
registry, rest := makeRegistry(&api.ResourceQuotaList{})
|
||||
registry.Object = &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
func TestResourceQuotaStrategy(t *testing.T) {
|
||||
if !Strategy.NamespaceScoped() {
|
||||
t.Errorf("ResourceQuota should be namespace scoped")
|
||||
}
|
||||
ctx := api.NewDefaultContext()
|
||||
obj, err := rest.Get(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
if Strategy.AllowCreateOnUpdate() {
|
||||
t.Errorf("ResourceQuota should not allow create on update")
|
||||
}
|
||||
if obj == nil {
|
||||
t.Errorf("unexpected nil object")
|
||||
}
|
||||
registry.Object = nil
|
||||
registry.Err = errors.NewNotFound("ResourceQuota", "bar")
|
||||
|
||||
obj, err = rest.Get(ctx, "bar")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if obj != nil {
|
||||
t.Errorf("unexpected object: %v", obj)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
_, rest := makeRegistry(&api.ResourceQuotaList{
|
||||
Items: []api.ResourceQuota{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ctx := api.NewDefaultContext()
|
||||
obj, err := rest.List(ctx, labels.Set{}.AsSelector(), fields.Set{}.AsSelector())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if obj == nil {
|
||||
t.Errorf("unexpected nil object")
|
||||
}
|
||||
list, ok := obj.(*api.ResourceQuotaList)
|
||||
if !ok || len(list.Items) != 1 {
|
||||
t.Errorf("unexpected list object: %v", obj)
|
||||
}
|
||||
|
||||
obj, err = rest.List(ctx, labels.Set{"foo": "bar"}.AsSelector(), fields.Set{}.AsSelector())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if obj == nil {
|
||||
t.Errorf("unexpected nil object")
|
||||
}
|
||||
list, ok = obj.(*api.ResourceQuotaList)
|
||||
if !ok || len(list.Items) != 0 {
|
||||
t.Errorf("unexpected list object: %v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
registry, rest := makeRegistry(&api.ResourceQuotaList{})
|
||||
resourceStatus := api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: *resource.NewQuantity(10.0, resource.BinarySI),
|
||||
},
|
||||
}
|
||||
registry.Object = &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"bar": "foo",
|
||||
},
|
||||
},
|
||||
Status: resourceStatus,
|
||||
}
|
||||
invalidUpdates := []struct {
|
||||
obj runtime.Object
|
||||
err error
|
||||
}{
|
||||
{&api.Pod{}, nil},
|
||||
{&api.ResourceQuota{ObjectMeta: api.ObjectMeta{Namespace: "$%#%"}}, nil},
|
||||
{&api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
}, fmt.Errorf("test error")},
|
||||
}
|
||||
for _, test := range invalidUpdates {
|
||||
registry.Err = test.err
|
||||
ctx := api.NewDefaultContext()
|
||||
_, _, err := rest.Update(ctx, test.obj)
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error for: %v", test.obj)
|
||||
}
|
||||
registry.Err = nil
|
||||
}
|
||||
|
||||
ctx := api.NewDefaultContext()
|
||||
update := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: *resource.NewQuantity(10.0, resource.BinarySI),
|
||||
},
|
||||
},
|
||||
resourceQuota := &api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Used: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("1"),
|
||||
api.ResourceMemory: resource.MustParse("1Gi"),
|
||||
api.ResourcePods: resource.MustParse("1"),
|
||||
api.ResourceServices: resource.MustParse("1"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("1"),
|
||||
api.ResourceQuotas: resource.MustParse("1"),
|
||||
},
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: *resource.NewQuantity(20.0, resource.BinarySI),
|
||||
api.ResourceCPU: resource.MustParse("100"),
|
||||
api.ResourceMemory: resource.MustParse("4Gi"),
|
||||
api.ResourcePods: resource.MustParse("10"),
|
||||
api.ResourceServices: resource.MustParse("10"),
|
||||
api.ResourceReplicationControllers: resource.MustParse("10"),
|
||||
api.ResourceQuotas: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
obj, _, err := rest.Update(ctx, update)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(obj.(*api.ResourceQuota).Labels, update.Labels) {
|
||||
t.Errorf("unexpected update object, labels don't match: %v vs %v", obj.(*api.ResourceQuota).Labels, update.Labels)
|
||||
}
|
||||
if !reflect.DeepEqual(obj.(*api.ResourceQuota).Spec, update.Spec) {
|
||||
t.Errorf("unexpected update object, specs don't match: %v vs %v", obj.(*api.ResourceQuota).Spec, update.Spec)
|
||||
}
|
||||
if !reflect.DeepEqual(obj.(*api.ResourceQuota).Status, registry.Object.(*api.ResourceQuota).Status) {
|
||||
t.Errorf("unexpected update object, status wasn't preserved: %v vs %v", obj.(*api.ResourceQuota).Status, registry.Object.(*api.ResourceQuota).Status)
|
||||
Strategy.ResetBeforeCreate(resourceQuota)
|
||||
if resourceQuota.Status.Used != nil {
|
||||
t.Errorf("ResourceQuota does not allow setting status on create")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
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 resourcequotausage provides Registry interface and it's REST
|
||||
// implementation for storing ResourceQuotaUsage api objects.
|
||||
package resourcequotausage
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
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 resourcequotausage
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// Registry contains the functions needed to support a ResourceQuotaUsage
|
||||
type Registry interface {
|
||||
// ApplyStatus should update the ResourceQuota.Status with latest observed state.
|
||||
// This should be atomic, and idempotent based on the ResourceVersion
|
||||
ApplyStatus(ctx api.Context, usage *api.ResourceQuotaUsage) error
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
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 resourcequotausage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// REST implements the RESTStorage interface for ResourceQuotaUsage
|
||||
type REST struct {
|
||||
registry Registry
|
||||
}
|
||||
|
||||
// NewREST creates a new REST backed by the given registry.
|
||||
func NewREST(registry Registry) *REST {
|
||||
return &REST{
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new resource observation object
|
||||
func (*REST) New() runtime.Object {
|
||||
return &api.ResourceQuotaUsage{}
|
||||
}
|
||||
|
||||
// Create takes the incoming ResourceQuotaUsage and applies the latest status atomically to a ResourceQuota
|
||||
func (b *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
resourceQuotaUsage, ok := obj.(*api.ResourceQuotaUsage)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("incorrect type: %#v", obj)
|
||||
}
|
||||
if err := b.registry.ApplyStatus(ctx, resourceQuotaUsage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &api.Status{Status: api.StatusSuccess}, nil
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
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 resourcequotausage
|
|
@ -114,11 +114,13 @@ func (rm *ResourceQuotaManager) syncResourceQuota(quota api.ResourceQuota) (err
|
|||
dirty := quota.Status.Hard == nil || quota.Status.Used == nil
|
||||
|
||||
// Create a usage object that is based on the quota resource version
|
||||
usage := api.ResourceQuotaUsage{
|
||||
usage := api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: quota.Name,
|
||||
Namespace: quota.Namespace,
|
||||
ResourceVersion: quota.ResourceVersion},
|
||||
ResourceVersion: quota.ResourceVersion,
|
||||
Labels: quota.Labels,
|
||||
Annotations: quota.Annotations},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{},
|
||||
Used: api.ResourceList{},
|
||||
|
@ -206,7 +208,8 @@ func (rm *ResourceQuotaManager) syncResourceQuota(quota api.ResourceQuota) (err
|
|||
|
||||
// update the usage only if it changed
|
||||
if dirty {
|
||||
return rm.kubeClient.ResourceQuotaUsages(usage.Namespace).Create(&usage)
|
||||
_, err = rm.kubeClient.ResourceQuotas(usage.Namespace).Status(&usage)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ func TestSyncResourceQuota(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
expectedUsage := api.ResourceQuotaUsage{
|
||||
expectedUsage := api.ResourceQuota{
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceCPU: resource.MustParse("3"),
|
||||
|
@ -166,7 +166,7 @@ func TestSyncResourceQuota(t *testing.T) {
|
|||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
usage := kubeClient.ResourceQuotaUsage
|
||||
usage := kubeClient.ResourceQuotaStatus
|
||||
|
||||
// ensure hard and used limits are what we expected
|
||||
for k, v := range expectedUsage.Status.Hard {
|
||||
|
|
|
@ -113,14 +113,16 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
|||
|
||||
if dirty {
|
||||
// construct a usage record
|
||||
usage := api.ResourceQuotaUsage{
|
||||
usage := api.ResourceQuota{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: quota.Name,
|
||||
Namespace: quota.Namespace,
|
||||
ResourceVersion: quota.ResourceVersion},
|
||||
ResourceVersion: quota.ResourceVersion,
|
||||
Labels: quota.Labels,
|
||||
Annotations: quota.Annotations},
|
||||
}
|
||||
usage.Status = *status
|
||||
err = q.client.ResourceQuotaUsages(usage.Namespace).Create(&usage)
|
||||
_, err = q.client.ResourceQuotas(usage.Namespace).Status(&usage)
|
||||
if err != nil {
|
||||
return apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), a.GetResource()))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue