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{},
|
&LimitRangeList{},
|
||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
|
||||||
&Namespace{},
|
&Namespace{},
|
||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
|
@ -86,7 +85,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
||||||
func (*LimitRangeList) IsAnAPIObject() {}
|
func (*LimitRangeList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
|
||||||
func (*Namespace) IsAnAPIObject() {}
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) IsAnAPIObject() {}
|
func (*Secret) IsAnAPIObject() {}
|
||||||
|
|
|
@ -1390,16 +1390,6 @@ type ResourceQuota struct {
|
||||||
Status ResourceQuotaStatus `json:"status,omitempty"`
|
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
|
// ResourceQuotaList is a list of ResourceQuota items
|
||||||
type ResourceQuotaList struct {
|
type ResourceQuotaList struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
|
@ -848,6 +848,9 @@ func init() {
|
||||||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(in *ResourceQuota, out *newer.ResourceQuota, s conversion.Scope) error {
|
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 {
|
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != 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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -57,7 +57,6 @@ func init() {
|
||||||
&LimitRangeList{},
|
&LimitRangeList{},
|
||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
|
||||||
&Namespace{},
|
&Namespace{},
|
||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
|
@ -93,7 +92,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
||||||
func (*LimitRangeList) IsAnAPIObject() {}
|
func (*LimitRangeList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
|
||||||
func (*Namespace) IsAnAPIObject() {}
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) IsAnAPIObject() {}
|
func (*Secret) IsAnAPIObject() {}
|
||||||
|
|
|
@ -1159,6 +1159,7 @@ type ResourceQuotaStatus struct {
|
||||||
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
||||||
type ResourceQuota struct {
|
type ResourceQuota struct {
|
||||||
TypeMeta `json:",inline"`
|
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 defines the desired quota
|
||||||
Spec ResourceQuotaSpec `json:"spec,omitempty" description:"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"`
|
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
|
// ResourceQuotaList is a list of ResourceQuota items
|
||||||
type ResourceQuotaList struct {
|
type ResourceQuotaList struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
|
@ -768,6 +768,9 @@ func init() {
|
||||||
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.Labels, &out.Labels, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(in *ResourceQuota, out *newer.ResourceQuota, s conversion.Scope) error {
|
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 {
|
if err := s.Convert(&in.Status, &out.Status, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != 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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -57,7 +57,6 @@ func init() {
|
||||||
&LimitRangeList{},
|
&LimitRangeList{},
|
||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
|
||||||
&Namespace{},
|
&Namespace{},
|
||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
|
@ -93,7 +92,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
||||||
func (*LimitRangeList) IsAnAPIObject() {}
|
func (*LimitRangeList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
|
||||||
func (*Namespace) IsAnAPIObject() {}
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) IsAnAPIObject() {}
|
func (*Secret) IsAnAPIObject() {}
|
||||||
|
|
|
@ -1221,7 +1221,7 @@ type ResourceQuotaStatus struct {
|
||||||
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
// ResourceQuota sets aggregate quota restrictions enforced per namespace
|
||||||
type ResourceQuota struct {
|
type ResourceQuota struct {
|
||||||
TypeMeta `json:",inline"`
|
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 defines the desired quota
|
||||||
Spec ResourceQuotaSpec `json:"spec,omitempty" description:"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"`
|
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
|
// ResourceQuotaList is a list of ResourceQuota items
|
||||||
type ResourceQuotaList struct {
|
type ResourceQuotaList struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
|
@ -51,7 +51,6 @@ func init() {
|
||||||
&LimitRangeList{},
|
&LimitRangeList{},
|
||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
|
||||||
&Namespace{},
|
&Namespace{},
|
||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
|
@ -87,7 +86,6 @@ func (*LimitRange) IsAnAPIObject() {}
|
||||||
func (*LimitRangeList) IsAnAPIObject() {}
|
func (*LimitRangeList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
|
||||||
func (*Namespace) IsAnAPIObject() {}
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) 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"`
|
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
|
// ResourceQuotaList is a list of ResourceQuota items
|
||||||
type ResourceQuotaList struct {
|
type ResourceQuotaList struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
|
@ -972,6 +972,36 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
||||||
return allErrs
|
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.
|
// ValidateNamespace tests if required fields are set.
|
||||||
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
|
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
|
||||||
allErrs := errs.ValidationErrorList{}
|
allErrs := errs.ValidationErrorList{}
|
||||||
|
|
|
@ -39,7 +39,6 @@ type Interface interface {
|
||||||
EventNamespacer
|
EventNamespacer
|
||||||
LimitRangesNamespacer
|
LimitRangesNamespacer
|
||||||
ResourceQuotasNamespacer
|
ResourceQuotasNamespacer
|
||||||
ResourceQuotaUsagesNamespacer
|
|
||||||
SecretsNamespacer
|
SecretsNamespacer
|
||||||
NamespacesInterface
|
NamespacesInterface
|
||||||
}
|
}
|
||||||
|
@ -76,10 +75,6 @@ func (c *Client) ResourceQuotas(namespace string) ResourceQuotaInterface {
|
||||||
return newResourceQuotas(c, namespace)
|
return newResourceQuotas(c, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterface {
|
|
||||||
return newResourceQuotaUsages(c, namespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Secrets(namespace string) SecretsInterface {
|
func (c *Client) Secrets(namespace string) SecretsInterface {
|
||||||
return newSecrets(c, namespace)
|
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
|
// 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.
|
// implementation. This makes faking out just the method you want to test easier.
|
||||||
type Fake struct {
|
type Fake struct {
|
||||||
Actions []FakeAction
|
Actions []FakeAction
|
||||||
PodsList api.PodList
|
PodsList api.PodList
|
||||||
CtrlList api.ReplicationControllerList
|
CtrlList api.ReplicationControllerList
|
||||||
Ctrl api.ReplicationController
|
Ctrl api.ReplicationController
|
||||||
ServiceList api.ServiceList
|
ServiceList api.ServiceList
|
||||||
EndpointsList api.EndpointsList
|
EndpointsList api.EndpointsList
|
||||||
MinionsList api.NodeList
|
MinionsList api.NodeList
|
||||||
EventsList api.EventList
|
EventsList api.EventList
|
||||||
LimitRangesList api.LimitRangeList
|
LimitRangesList api.LimitRangeList
|
||||||
ResourceQuotasList api.ResourceQuotaList
|
ResourceQuotaStatus api.ResourceQuota
|
||||||
ResourceQuotaUsage api.ResourceQuotaUsage
|
ResourceQuotasList api.ResourceQuotaList
|
||||||
NamespacesList api.NamespaceList
|
NamespacesList api.NamespaceList
|
||||||
SecretList api.SecretList
|
SecretList api.SecretList
|
||||||
Secret api.Secret
|
Secret api.Secret
|
||||||
Err error
|
Err error
|
||||||
Watch watch.Interface
|
Watch watch.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Fake) LimitRanges(namespace string) LimitRangeInterface {
|
func (c *Fake) LimitRanges(namespace string) LimitRangeInterface {
|
||||||
|
@ -60,10 +60,6 @@ func (c *Fake) ResourceQuotas(namespace string) ResourceQuotaInterface {
|
||||||
return &FakeResourceQuotas{Fake: c, Namespace: namespace}
|
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 {
|
func (c *Fake) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||||
return &FakeReplicationControllers{Fake: c, Namespace: namespace}
|
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
|
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) {
|
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})
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-resourceQuota", Value: resourceVersion})
|
||||||
return c.Fake.Watch, nil
|
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
|
Delete(name string) error
|
||||||
Create(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error)
|
Create(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error)
|
||||||
Update(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)
|
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
|
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) {
|
func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) {
|
||||||
result = &api.ResourceQuota{}
|
result = &api.ResourceQuota{}
|
||||||
if len(resourceQuota.ResourceVersion) == 0 {
|
if len(resourceQuota.ResourceVersion) == 0 {
|
||||||
|
@ -95,6 +96,17 @@ func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.R
|
||||||
return
|
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
|
// Watch returns a watch.Interface that watches the requested resource
|
||||||
func (c *resourceQuotas) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
func (c *resourceQuotas) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||||
return c.r.Get().
|
return c.r.Get().
|
||||||
|
|
|
@ -139,6 +139,33 @@ func TestResourceQuotaUpdate(t *testing.T) {
|
||||||
c.Validate(t, response, err)
|
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) {
|
func TestInvalidResourceQuotaUpdate(t *testing.T) {
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
resourceQuota := &api.ResourceQuota{
|
resourceQuota := &api.ResourceQuota{
|
||||||
|
|
|
@ -51,8 +51,7 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||||
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
|
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"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()))
|
eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds()))
|
||||||
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
|
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
|
||||||
resourceQuotaRegistry := resourcequota.NewEtcdRegistry(c.EtcdHelper)
|
|
||||||
|
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(c.EtcdHelper)
|
||||||
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
|
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
|
||||||
m.namespaceRegistry = namespace.NewEtcdRegistry(c.EtcdHelper)
|
m.namespaceRegistry = namespace.NewEtcdRegistry(c.EtcdHelper)
|
||||||
|
|
||||||
|
@ -418,11 +418,11 @@ func (m *Master) init(c *Config) {
|
||||||
"nodes": nodeStorage,
|
"nodes": nodeStorage,
|
||||||
"events": event.NewREST(eventRegistry),
|
"events": event.NewREST(eventRegistry),
|
||||||
|
|
||||||
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
||||||
"resourceQuotas": resourcequota.NewREST(resourceQuotaRegistry),
|
"resourceQuotas": resourceQuotaStorage,
|
||||||
"resourceQuotaUsages": resourcequotausage.NewREST(resourceQuotaRegistry),
|
"resourceQuotas/status": resourceQuotaStatusStorage,
|
||||||
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
||||||
"secrets": secret.NewREST(secretRegistry),
|
"secrets": secret.NewREST(secretRegistry),
|
||||||
}
|
}
|
||||||
|
|
||||||
apiVersions := []string{"v1beta1", "v1beta2"}
|
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
|
package resourcequota
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"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 {
|
type Registry interface {
|
||||||
generic.Registry
|
// ListResourceQuotas obtains a list of pods having labels which match selector.
|
||||||
resourcequotausage.Registry
|
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.
|
// Storage is an interface for a standard REST Storage backend
|
||||||
type registry struct {
|
// TODO: move me somewhere common
|
||||||
*etcdgeneric.Etcd
|
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
|
// storage puts strong typing around storage calls
|
||||||
func (r *registry) ApplyStatus(ctx api.Context, usage *api.ResourceQuotaUsage) error {
|
type storage struct {
|
||||||
obj, err := r.Get(ctx, usage.Name)
|
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 {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return obj.(*api.ResourceQuotaList), nil
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEtcdRegistry returns a registry which will store ResourceQuota in the given helper
|
func (s *storage) WatchResourceQuotas(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
|
||||||
func NewEtcdRegistry(h tools.EtcdHelper) Registry {
|
return s.Watch(ctx, label, field, resourceVersion)
|
||||||
return ®istry{
|
}
|
||||||
Etcd: &etcdgeneric.Etcd{
|
|
||||||
NewFunc: func() runtime.Object { return &api.ResourceQuota{} },
|
func (s *storage) GetResourceQuota(ctx api.Context, podID string) (*api.ResourceQuota, error) {
|
||||||
NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} },
|
obj, err := s.Get(ctx, podID)
|
||||||
EndpointName: "resourcequotas",
|
if err != nil {
|
||||||
KeyRootFunc: func(ctx api.Context) string {
|
return nil, err
|
||||||
return etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/resourcequotas")
|
}
|
||||||
},
|
return obj.(*api.ResourceQuota), nil
|
||||||
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
}
|
||||||
return etcdgeneric.NamespaceKeyFunc(ctx, "/registry/resourcequotas", id)
|
|
||||||
},
|
func (s *storage) CreateResourceQuota(ctx api.Context, pod *api.ResourceQuota) error {
|
||||||
Helper: h,
|
_, 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/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"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.
|
// resourcequotaStrategy implements behavior for ResourceQuota objects
|
||||||
type REST struct {
|
type resourcequotaStrategy struct {
|
||||||
registry generic.Registry
|
runtime.ObjectTyper
|
||||||
|
api.NameGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewREST returns a new REST. You must use a registry created by
|
// Strategy is the default logic that applies when creating and updating ResourceQuota
|
||||||
// NewEtcdRegistry unless you're testing.
|
// objects via the REST API.
|
||||||
func NewREST(registry generic.Registry) *REST {
|
var Strategy = resourcequotaStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||||
return &REST{
|
|
||||||
registry: registry,
|
// NamespaceScoped is true for resourcequotas.
|
||||||
}
|
func (resourcequotaStrategy) NamespaceScoped() bool {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a ResourceQuota object
|
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
func (resourcequotaStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
resourcequota := obj.(*api.ResourceQuota)
|
||||||
if !ok {
|
resourcequota.Status = api.ResourceQuotaStatus{}
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a ResourceQuota object.
|
// Validate validates a new resourcequota.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
func (resourcequotaStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
resourcequota := obj.(*api.ResourceQuota)
|
||||||
if !ok {
|
return validation.ValidateResourceQuota(resourcequota)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the ResourceQuota with the specified name
|
// AllowCreateOnUpdate is false for resourcequotas.
|
||||||
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
|
func (resourcequotaStrategy) AllowCreateOnUpdate() bool {
|
||||||
obj, err := rs.registry.Get(ctx, name)
|
return false
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
|
// 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
|
package resourcequota
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"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/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) {
|
func TestResourceQuotaStrategy(t *testing.T) {
|
||||||
registry := registrytest.NewGeneric(resourceList)
|
if !Strategy.NamespaceScoped() {
|
||||||
rest := NewREST(registry)
|
t.Errorf("ResourceQuota should be namespace scoped")
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
if Strategy.AllowCreateOnUpdate() {
|
||||||
obj, err := rest.Get(ctx, "foo")
|
t.Errorf("ResourceQuota should not allow create on update")
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
}
|
||||||
if obj == nil {
|
resourceQuota := &api.ResourceQuota{
|
||||||
t.Errorf("unexpected nil object")
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
}
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Status: api.ResourceQuotaStatus{
|
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{
|
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)
|
Strategy.ResetBeforeCreate(resourceQuota)
|
||||||
if err != nil {
|
if resourceQuota.Status.Used != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("ResourceQuota does not allow setting status on create")
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
dirty := quota.Status.Hard == nil || quota.Status.Used == nil
|
||||||
|
|
||||||
// Create a usage object that is based on the quota resource version
|
// Create a usage object that is based on the quota resource version
|
||||||
usage := api.ResourceQuotaUsage{
|
usage := api.ResourceQuota{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: quota.Name,
|
Name: quota.Name,
|
||||||
Namespace: quota.Namespace,
|
Namespace: quota.Namespace,
|
||||||
ResourceVersion: quota.ResourceVersion},
|
ResourceVersion: quota.ResourceVersion,
|
||||||
|
Labels: quota.Labels,
|
||||||
|
Annotations: quota.Annotations},
|
||||||
Status: api.ResourceQuotaStatus{
|
Status: api.ResourceQuotaStatus{
|
||||||
Hard: api.ResourceList{},
|
Hard: api.ResourceList{},
|
||||||
Used: api.ResourceList{},
|
Used: api.ResourceList{},
|
||||||
|
@ -206,7 +208,8 @@ func (rm *ResourceQuotaManager) syncResourceQuota(quota api.ResourceQuota) (err
|
||||||
|
|
||||||
// update the usage only if it changed
|
// update the usage only if it changed
|
||||||
if dirty {
|
if dirty {
|
||||||
return rm.kubeClient.ResourceQuotaUsages(usage.Namespace).Create(&usage)
|
_, err = rm.kubeClient.ResourceQuotas(usage.Namespace).Status(&usage)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ func TestSyncResourceQuota(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedUsage := api.ResourceQuotaUsage{
|
expectedUsage := api.ResourceQuota{
|
||||||
Status: api.ResourceQuotaStatus{
|
Status: api.ResourceQuotaStatus{
|
||||||
Hard: api.ResourceList{
|
Hard: api.ResourceList{
|
||||||
api.ResourceCPU: resource.MustParse("3"),
|
api.ResourceCPU: resource.MustParse("3"),
|
||||||
|
@ -166,7 +166,7 @@ func TestSyncResourceQuota(t *testing.T) {
|
||||||
t.Errorf("Unexpected error %v", err)
|
t.Errorf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
usage := kubeClient.ResourceQuotaUsage
|
usage := kubeClient.ResourceQuotaStatus
|
||||||
|
|
||||||
// ensure hard and used limits are what we expected
|
// ensure hard and used limits are what we expected
|
||||||
for k, v := range expectedUsage.Status.Hard {
|
for k, v := range expectedUsage.Status.Hard {
|
||||||
|
|
|
@ -113,14 +113,16 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
||||||
|
|
||||||
if dirty {
|
if dirty {
|
||||||
// construct a usage record
|
// construct a usage record
|
||||||
usage := api.ResourceQuotaUsage{
|
usage := api.ResourceQuota{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: quota.Name,
|
Name: quota.Name,
|
||||||
Namespace: quota.Namespace,
|
Namespace: quota.Namespace,
|
||||||
ResourceVersion: quota.ResourceVersion},
|
ResourceVersion: quota.ResourceVersion,
|
||||||
|
Labels: quota.Labels,
|
||||||
|
Annotations: quota.Annotations},
|
||||||
}
|
}
|
||||||
usage.Status = *status
|
usage.Status = *status
|
||||||
err = q.client.ResourceQuotaUsages(usage.Namespace).Create(&usage)
|
_, err = q.client.ResourceQuotas(usage.Namespace).Status(&usage)
|
||||||
if err != nil {
|
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()))
|
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