Improve error message when name is omitted but generateName is available

This commit is contained in:
derekwaynecarr 2015-04-14 18:03:26 -04:00
parent 4d3b66c09f
commit 81dcd8c836
17 changed files with 184 additions and 81 deletions

View File

@ -21,14 +21,16 @@ import (
) )
type attributesRecord struct { type attributesRecord struct {
kind string
namespace string namespace string
resource string resource string
operation string operation string
object runtime.Object object runtime.Object
} }
func NewAttributesRecord(object runtime.Object, namespace, resource, operation string) Attributes { func NewAttributesRecord(object runtime.Object, kind, namespace, resource, operation string) Attributes {
return &attributesRecord{ return &attributesRecord{
kind: kind,
namespace: namespace, namespace: namespace,
resource: resource, resource: resource,
operation: operation, operation: operation,
@ -36,6 +38,10 @@ func NewAttributesRecord(object runtime.Object, namespace, resource, operation s
} }
} }
func (record *attributesRecord) GetKind() string {
return record.kind
}
func (record *attributesRecord) GetNamespace() string { func (record *attributesRecord) GetNamespace() string {
return record.namespace return record.namespace
} }

48
pkg/admission/errors.go Normal file
View File

@ -0,0 +1,48 @@
/*
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 admission
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
)
// NewForbidden is a utility function to return a well-formatted admission control error response
func NewForbidden(a Attributes, internalError error) error {
// do not double wrap an error of same type
if apierrors.IsForbidden(internalError) {
return internalError
}
name := "Unknown"
kind := a.GetKind()
obj := a.GetObject()
if obj != nil {
objectMeta, err := api.ObjectMetaFor(obj)
if err != nil {
return apierrors.NewForbidden(kind, name, err)
}
// this is necessary because name object name generation has not occurred yet
if len(objectMeta.Name) > 0 {
name = objectMeta.Name
} else if len(objectMeta.GenerateName) > 0 {
name = objectMeta.GenerateName
}
}
return apierrors.NewForbidden(kind, name, internalError)
}

View File

@ -27,6 +27,7 @@ type Attributes interface {
GetResource() string GetResource() string
GetOperation() string GetOperation() string
GetObject() runtime.Object GetObject() runtime.Object
GetKind() string
} }
// Interface is an abstract, pluggable interface for Admission Control decisions. // Interface is an abstract, pluggable interface for Admission Control decisions.

View File

@ -40,6 +40,8 @@ type Interface interface {
SetNamespace(namespace string) SetNamespace(namespace string)
Name() string Name() string
SetName(name string) SetName(name string)
GenerateName() string
SetGenerateName(name string)
UID() types.UID UID() types.UID
SetUID(uid types.UID) SetUID(uid types.UID)
ResourceVersion() string ResourceVersion() string
@ -79,6 +81,9 @@ type MetadataAccessor interface {
Name(obj runtime.Object) (string, error) Name(obj runtime.Object) (string, error)
SetName(obj runtime.Object, name string) error SetName(obj runtime.Object, name string) error
GenerateName(obj runtime.Object) (string, error)
SetGenerateName(obj runtime.Object, name string) error
UID(obj runtime.Object) (types.UID, error) UID(obj runtime.Object) (types.UID, error)
SetUID(obj runtime.Object, uid types.UID) error SetUID(obj runtime.Object, uid types.UID) error

View File

@ -178,6 +178,23 @@ func (resourceAccessor) SetName(obj runtime.Object, name string) error {
return nil return nil
} }
func (resourceAccessor) GenerateName(obj runtime.Object) (string, error) {
accessor, err := Accessor(obj)
if err != nil {
return "", err
}
return accessor.GenerateName(), nil
}
func (resourceAccessor) SetGenerateName(obj runtime.Object, name string) error {
accessor, err := Accessor(obj)
if err != nil {
return err
}
accessor.SetGenerateName(name)
return nil
}
func (resourceAccessor) UID(obj runtime.Object) (types.UID, error) { func (resourceAccessor) UID(obj runtime.Object) (types.UID, error) {
accessor, err := Accessor(obj) accessor, err := Accessor(obj)
if err != nil { if err != nil {
@ -268,6 +285,7 @@ func (resourceAccessor) SetResourceVersion(obj runtime.Object, version string) e
type genericAccessor struct { type genericAccessor struct {
namespace *string namespace *string
name *string name *string
generateName *string
uid *types.UID uid *types.UID
apiVersion *string apiVersion *string
kind *string kind *string
@ -305,6 +323,20 @@ func (a genericAccessor) SetName(name string) {
*a.name = name *a.name = name
} }
func (a genericAccessor) GenerateName() string {
if a.generateName == nil {
return ""
}
return *a.generateName
}
func (a genericAccessor) SetGenerateName(generateName string) {
if a.generateName == nil {
return
}
*a.generateName = generateName
}
func (a genericAccessor) UID() types.UID { func (a genericAccessor) UID() types.UID {
if a.uid == nil { if a.uid == nil {
return "" return ""
@ -392,6 +424,9 @@ func extractFromObjectMeta(v reflect.Value, a *genericAccessor) error {
if err := runtime.FieldPtr(v, "Name", &a.name); err != nil { if err := runtime.FieldPtr(v, "Name", &a.name); err != nil {
return err return err
} }
if err := runtime.FieldPtr(v, "GenerateName", &a.generateName); err != nil {
return err
}
if err := runtime.FieldPtr(v, "UID", &a.uid); err != nil { if err := runtime.FieldPtr(v, "UID", &a.uid); err != nil {
return err return err
} }

View File

@ -29,6 +29,7 @@ func TestGenericTypeMeta(t *testing.T) {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
GenerateName string `json:"generateName,omitempty"`
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
CreationTimestamp util.Time `json:"creationTimestamp,omitempty"` CreationTimestamp util.Time `json:"creationTimestamp,omitempty"`
SelfLink string `json:"selfLink,omitempty"` SelfLink string `json:"selfLink,omitempty"`
@ -44,6 +45,7 @@ func TestGenericTypeMeta(t *testing.T) {
TypeMeta{ TypeMeta{
Namespace: "bar", Namespace: "bar",
Name: "foo", Name: "foo",
GenerateName: "prefix",
UID: "uid", UID: "uid",
APIVersion: "a", APIVersion: "a",
Kind: "b", Kind: "b",
@ -63,6 +65,9 @@ func TestGenericTypeMeta(t *testing.T) {
if e, a := "foo", accessor.Name(); e != a { if e, a := "foo", accessor.Name(); e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := "prefix", accessor.GenerateName(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "uid", string(accessor.UID()); e != a { if e, a := "uid", string(accessor.UID()); e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
@ -92,6 +97,7 @@ func TestGenericTypeMeta(t *testing.T) {
accessor.SetNamespace("baz") accessor.SetNamespace("baz")
accessor.SetName("bar") accessor.SetName("bar")
accessor.SetGenerateName("generate")
accessor.SetUID("other") accessor.SetUID("other")
accessor.SetAPIVersion("c") accessor.SetAPIVersion("c")
accessor.SetKind("d") accessor.SetKind("d")
@ -105,6 +111,9 @@ func TestGenericTypeMeta(t *testing.T) {
if e, a := "bar", j.Name; e != a { if e, a := "bar", j.Name; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := "generate", j.GenerateName; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "other", j.UID; e != a { if e, a := "other", j.UID; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
@ -135,6 +144,7 @@ type InternalTypeMeta struct {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
GenerateName string `json:"generateName,omitempty"`
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
CreationTimestamp util.Time `json:"creationTimestamp,omitempty"` CreationTimestamp util.Time `json:"creationTimestamp,omitempty"`
SelfLink string `json:"selfLink,omitempty"` SelfLink string `json:"selfLink,omitempty"`
@ -154,6 +164,7 @@ func TestGenericTypeMetaAccessor(t *testing.T) {
InternalTypeMeta{ InternalTypeMeta{
Namespace: "bar", Namespace: "bar",
Name: "foo", Name: "foo",
GenerateName: "prefix",
UID: "uid", UID: "uid",
APIVersion: "a", APIVersion: "a",
Kind: "b", Kind: "b",
@ -178,6 +189,13 @@ func TestGenericTypeMetaAccessor(t *testing.T) {
if e, a := "foo", name; e != a { if e, a := "foo", name; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
generateName, err := accessor.GenerateName(j)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if e, a := "prefix", generateName; e != a {
t.Errorf("expected %v, got %v", e, a)
}
uid, err := accessor.UID(j) uid, err := accessor.UID(j)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -234,6 +252,9 @@ func TestGenericTypeMetaAccessor(t *testing.T) {
if err := accessor.SetName(j, "bar"); err != nil { if err := accessor.SetName(j, "bar"); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if err := accessor.SetGenerateName(j, "generate"); err != nil {
t.Errorf("unexpected error: %v", err)
}
if err := accessor.SetUID(j, "other"); err != nil { if err := accessor.SetUID(j, "other"); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -264,6 +285,9 @@ func TestGenericTypeMetaAccessor(t *testing.T) {
if e, a := "bar", j.TypeMeta.Name; e != a { if e, a := "bar", j.TypeMeta.Name; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := "generate", j.TypeMeta.GenerateName; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "other", j.TypeMeta.UID; e != a { if e, a := "other", j.TypeMeta.UID; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
@ -295,6 +319,7 @@ func TestGenericObjectMeta(t *testing.T) {
type ObjectMeta struct { type ObjectMeta struct {
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
GenerateName string `json:"generateName,omitempty"`
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
CreationTimestamp util.Time `json:"creationTimestamp,omitempty"` CreationTimestamp util.Time `json:"creationTimestamp,omitempty"`
SelfLink string `json:"selfLink,omitempty"` SelfLink string `json:"selfLink,omitempty"`
@ -314,6 +339,7 @@ func TestGenericObjectMeta(t *testing.T) {
ObjectMeta{ ObjectMeta{
Namespace: "bar", Namespace: "bar",
Name: "foo", Name: "foo",
GenerateName: "prefix",
UID: "uid", UID: "uid",
ResourceVersion: "1", ResourceVersion: "1",
SelfLink: "some/place/only/we/know", SelfLink: "some/place/only/we/know",
@ -331,6 +357,9 @@ func TestGenericObjectMeta(t *testing.T) {
if e, a := "foo", accessor.Name(); e != a { if e, a := "foo", accessor.Name(); e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := "prefix", accessor.GenerateName(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "uid", string(accessor.UID()); e != a { if e, a := "uid", string(accessor.UID()); e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
@ -355,6 +384,7 @@ func TestGenericObjectMeta(t *testing.T) {
accessor.SetNamespace("baz") accessor.SetNamespace("baz")
accessor.SetName("bar") accessor.SetName("bar")
accessor.SetGenerateName("generate")
accessor.SetUID("other") accessor.SetUID("other")
accessor.SetAPIVersion("c") accessor.SetAPIVersion("c")
accessor.SetKind("d") accessor.SetKind("d")
@ -370,6 +400,9 @@ func TestGenericObjectMeta(t *testing.T) {
if e, a := "bar", j.Name; e != a { if e, a := "bar", j.Name; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := "generate", j.GenerateName; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "other", j.UID; e != a { if e, a := "other", j.UID; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }

View File

@ -216,7 +216,7 @@ func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectType
return return
} }
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "CREATE")) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, scope.Resource, "CREATE"))
if err != nil { if err != nil {
errorJSON(err, scope.Codec, w) errorJSON(err, scope.Codec, w)
return return
@ -262,7 +262,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper
obj := r.New() obj := r.New()
// PATCH requires same permission as UPDATE // PATCH requires same permission as UPDATE
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "UPDATE")) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, scope.Resource, "UPDATE"))
if err != nil { if err != nil {
errorJSON(err, scope.Codec, w) errorJSON(err, scope.Codec, w)
return return
@ -362,7 +362,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
return return
} }
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "UPDATE")) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, scope.Resource, "UPDATE"))
if err != nil { if err != nil {
errorJSON(err, scope.Codec, w) errorJSON(err, scope.Codec, w)
return return
@ -423,7 +423,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope,
} }
} }
err = admit.Admit(admission.NewAttributesRecord(nil, namespace, scope.Resource, "DELETE")) err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, scope.Resource, "DELETE"))
if err != nil { if err != nil {
errorJSON(err, scope.Codec, w) errorJSON(err, scope.Codec, w)
return return

View File

@ -21,7 +21,6 @@ import (
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
@ -36,7 +35,7 @@ func init() {
type alwaysDeny struct{} type alwaysDeny struct{}
func (alwaysDeny) Admit(a admission.Attributes) (err error) { func (alwaysDeny) Admit(a admission.Attributes) (err error) {
return apierrors.NewForbidden(a.GetResource(), "", errors.New("Admission control is denying all modifications")) return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
} }
func NewAlwaysDeny() admission.Interface { func NewAlwaysDeny() admission.Interface {

View File

@ -24,7 +24,7 @@ import (
func TestAdmission(t *testing.T) { func TestAdmission(t *testing.T) {
handler := NewAlwaysDeny() handler := NewAlwaysDeny()
err := handler.Admit(admission.NewAttributesRecord(nil, "foo", "Pod", "ignored")) err := handler.Admit(admission.NewAttributesRecord(nil, "Pod", "foo", "Pod", "ignored"))
if err == nil { if err == nil {
t.Errorf("Expected error returned from admission handler") t.Errorf("Expected error returned from admission handler")
} }

View File

@ -22,7 +22,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
@ -58,6 +57,9 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
name := "Unknown" name := "Unknown"
if obj != nil { if obj != nil {
name, _ = meta.NewAccessor().Name(obj) name, _ = meta.NewAccessor().Name(obj)
if len(name) == 0 {
name, _ = meta.NewAccessor().GenerateName(obj)
}
} }
key := &api.LimitRange{ key := &api.LimitRange{
@ -68,7 +70,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
} }
items, err := l.indexer.Index("namespace", key) items, err := l.indexer.Index("namespace", key)
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 limit ranges", a.GetOperation(), resource)) return admission.NewForbidden(a, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing limit ranges", a.GetOperation(), resource))
} }
if len(items) == 0 { if len(items) == 0 {
return nil return nil
@ -79,7 +81,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
limitRange := items[i].(*api.LimitRange) limitRange := items[i].(*api.LimitRange)
err = l.limitFunc(limitRange, a.GetResource(), a.GetObject()) err = l.limitFunc(limitRange, a.GetResource(), a.GetObject())
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
} }
return nil return nil
@ -250,11 +252,11 @@ func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
switch minOrMax { switch minOrMax {
case "Min": case "Min":
if observed < enforced { if observed < enforced {
return apierrors.NewForbidden("pods", pod.Name, err) return err
} }
case "Max": case "Max":
if observed > enforced { if observed > enforced {
return apierrors.NewForbidden("pods", pod.Name, err) return err
} }
} }
} }

View File

@ -53,11 +53,11 @@ func (p *provision) Admit(a admission.Attributes) (err error) {
} }
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if mapping.Scope.Name() != meta.RESTScopeNameNamespace { if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
return nil return nil
@ -71,14 +71,14 @@ func (p *provision) Admit(a admission.Attributes) (err error) {
} }
_, exists, err := p.store.Get(namespace) _, exists, err := p.store.Get(namespace)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if exists { if exists {
return nil return nil
} }
_, err = p.client.Namespaces().Create(namespace) _, err = p.client.Namespaces().Create(namespace)
if err != nil && !errors.IsAlreadyExists(err) { if err != nil && !errors.IsAlreadyExists(err) {
return err return admission.NewForbidden(a, err)
} }
return nil return nil
} }

View File

@ -41,7 +41,7 @@ func TestAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},
}, },
} }
err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "CREATE")) err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler") t.Errorf("Unexpected error returned from admission handler")
} }
@ -72,7 +72,7 @@ func TestAdmissionNamespaceExists(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},
}, },
} }
err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "CREATE")) err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler") t.Errorf("Unexpected error returned from admission handler")
} }
@ -96,7 +96,7 @@ func TestIgnoreAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},
}, },
} }
err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "UPDATE")) err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "UPDATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler") t.Errorf("Unexpected error returned from admission handler")
} }
@ -123,7 +123,7 @@ func TestAdmissionNamespaceExistsUnknownToHandler(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},
}, },
} }
err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "CREATE")) err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler") t.Errorf("Unexpected error returned from admission handler")
} }

View File

@ -22,7 +22,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
@ -50,11 +49,11 @@ type exists struct {
func (e *exists) Admit(a admission.Attributes) (err error) { func (e *exists) Admit(a admission.Attributes) (err error) {
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if mapping.Scope.Name() != meta.RESTScopeNameNamespace { if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
return nil return nil
@ -68,17 +67,12 @@ func (e *exists) Admit(a admission.Attributes) (err error) {
} }
_, exists, err := e.store.Get(namespace) _, exists, err := e.store.Get(namespace)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if exists { if exists {
return nil return nil
} }
obj := a.GetObject() return admission.NewForbidden(a, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
name := "Unknown"
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
}
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
} }
func NewExists(c client.Interface) admission.Interface { func NewExists(c client.Interface) admission.Interface {

View File

@ -22,7 +22,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
@ -52,11 +51,11 @@ func (l *lifecycle) Admit(a admission.Attributes) (err error) {
} }
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if mapping.Scope.Name() != meta.RESTScopeNameNamespace { if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
return nil return nil
@ -68,7 +67,7 @@ func (l *lifecycle) Admit(a admission.Attributes) (err error) {
}, },
}) })
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if !exists { if !exists {
return nil return nil
@ -78,12 +77,7 @@ func (l *lifecycle) Admit(a admission.Attributes) (err error) {
return nil return nil
} }
name := "Unknown" return admission.NewForbidden(a, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
obj := a.GetObject()
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
}
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
} }
func NewLifecycle(c client.Interface) admission.Interface { func NewLifecycle(c client.Interface) admission.Interface {

View File

@ -50,7 +50,7 @@ func TestAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},
}, },
} }
err := handler.Admit(admission.NewAttributesRecord(&pod, namespaceObj.Namespace, "pods", "CREATE")) err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "CREATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err) t.Errorf("Unexpected error returned from admission handler: %v", err)
} }
@ -60,19 +60,19 @@ func TestAdmission(t *testing.T) {
store.Add(namespaceObj) store.Add(namespaceObj)
// verify create operations in the namespace cause an error // verify create operations in the namespace cause an error
err = handler.Admit(admission.NewAttributesRecord(&pod, namespaceObj.Namespace, "pods", "CREATE")) err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "CREATE"))
if err == nil { if err == nil {
t.Errorf("Expected error rejecting creates in a namespace when it is terminating") t.Errorf("Expected error rejecting creates in a namespace when it is terminating")
} }
// verify update operations in the namespace can proceed // verify update operations in the namespace can proceed
err = handler.Admit(admission.NewAttributesRecord(&pod, namespaceObj.Namespace, "pods", "UPDATE")) err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "UPDATE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err) t.Errorf("Unexpected error returned from admission handler: %v", err)
} }
// verify delete operations in the namespace can proceed // verify delete operations in the namespace can proceed
err = handler.Admit(admission.NewAttributesRecord(nil, namespaceObj.Namespace, "pods", "DELETE")) err = handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespaceObj.Namespace, "pods", "DELETE"))
if err != nil { if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err) t.Errorf("Unexpected error returned from admission handler: %v", err)
} }

View File

@ -22,8 +22,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
@ -71,13 +69,6 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
return nil return nil
} }
obj := a.GetObject()
resource := a.GetResource()
name := "Unknown"
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
}
key := &api.ResourceQuota{ key := &api.ResourceQuota{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Namespace: a.GetNamespace(), Namespace: a.GetNamespace(),
@ -86,7 +77,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
} }
items, err := q.indexer.Index("namespace", key) items, err := q.indexer.Index("namespace", key)
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(), resource)) return admission.NewForbidden(a, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), a.GetResource()))
} }
if len(items) == 0 { if len(items) == 0 {
return nil return nil
@ -109,7 +100,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
dirty, err := IncrementUsage(a, status, q.client) dirty, err := IncrementUsage(a, status, q.client)
if err != nil { if err != nil {
return err return admission.NewForbidden(a, err)
} }
if dirty { if dirty {
@ -125,7 +116,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
usage.Status = *status usage.Status = *status
_, err = q.client.ResourceQuotas(usage.Namespace).UpdateStatus(&usage) _, err = q.client.ResourceQuotas(usage.Namespace).UpdateStatus(&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 admission.NewForbidden(a, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), a.GetResource()))
} }
} }
} }
@ -136,17 +127,12 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
// Return true if the usage must be recorded prior to admitting the new resource // Return true if the usage must be recorded prior to admitting the new resource
// Return an error if the operation should not pass admission control // Return an error if the operation should not pass admission control
func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, client client.Interface) (bool, error) { func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, client client.Interface) (bool, error) {
obj := a.GetObject()
resourceName := a.GetResource()
name := "Unknown"
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
}
dirty := false dirty := false
set := map[api.ResourceName]bool{} set := map[api.ResourceName]bool{}
for k := range status.Hard { for k := range status.Hard {
set[k] = true set[k] = true
} }
obj := a.GetObject()
// handle max counts for each kind of resource (pods, services, replicationControllers, etc.) // handle max counts for each kind of resource (pods, services, replicationControllers, etc.)
if a.GetOperation() == "CREATE" { if a.GetOperation() == "CREATE" {
resourceName := resourceToResourceName[a.GetResource()] resourceName := resourceToResourceName[a.GetResource()]
@ -154,10 +140,10 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
if hardFound { if hardFound {
used, usedFound := status.Used[resourceName] used, usedFound := status.Used[resourceName]
if !usedFound { if !usedFound {
return false, apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")) return false, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")
} }
if used.Value() >= hard.Value() { if used.Value() >= hard.Value() {
return false, apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Limited to %s %s", hard.String(), a.GetResource())) return false, fmt.Errorf("Limited to %s %s", hard.String(), resourceName)
} else { } else {
status.Used[resourceName] = *resource.NewQuantity(used.Value()+int64(1), resource.DecimalSI) status.Used[resourceName] = *resource.NewQuantity(used.Value()+int64(1), resource.DecimalSI)
dirty = true dirty = true
@ -173,7 +159,7 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
if a.GetOperation() == "UPDATE" { if a.GetOperation() == "UPDATE" {
oldPod, err := client.Pods(a.GetNamespace()).Get(pod.Name) oldPod, err := client.Pods(a.GetNamespace()).Get(pod.Name)
if err != nil { if err != nil {
return false, apierrors.NewForbidden(resourceName, name, err) return false, err
} }
oldCPU := resourcequota.PodCPU(oldPod) oldCPU := resourcequota.PodCPU(oldPod)
oldMemory := resourcequota.PodMemory(oldPod) oldMemory := resourcequota.PodMemory(oldPod)
@ -185,10 +171,10 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
if hardMemFound { if hardMemFound {
used, usedFound := status.Used[api.ResourceMemory] used, usedFound := status.Used[api.ResourceMemory]
if !usedFound { if !usedFound {
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")) return false, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")
} }
if used.Value()+deltaMemory.Value() > hardMem.Value() { if used.Value()+deltaMemory.Value() > hardMem.Value() {
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Limited to %s memory", hardMem.String())) return false, fmt.Errorf("Limited to %s memory", hardMem.String())
} else { } else {
status.Used[api.ResourceMemory] = *resource.NewQuantity(used.Value()+deltaMemory.Value(), resource.DecimalSI) status.Used[api.ResourceMemory] = *resource.NewQuantity(used.Value()+deltaMemory.Value(), resource.DecimalSI)
dirty = true dirty = true
@ -198,10 +184,10 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
if hardCPUFound { if hardCPUFound {
used, usedFound := status.Used[api.ResourceCPU] used, usedFound := status.Used[api.ResourceCPU]
if !usedFound { if !usedFound {
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")) return false, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.")
} }
if used.MilliValue()+deltaCPU.MilliValue() > hardCPU.MilliValue() { if used.MilliValue()+deltaCPU.MilliValue() > hardCPU.MilliValue() {
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Limited to %s CPU", hardCPU.String())) return false, fmt.Errorf("Limited to %s CPU", hardCPU.String())
} else { } else {
status.Used[api.ResourceCPU] = *resource.NewMilliQuantity(used.MilliValue()+deltaCPU.MilliValue(), resource.DecimalSI) status.Used[api.ResourceCPU] = *resource.NewMilliQuantity(used.MilliValue()+deltaCPU.MilliValue(), resource.DecimalSI)
dirty = true dirty = true

View File

@ -41,7 +41,7 @@ func getResourceRequirements(cpu, memory string) api.ResourceRequirements {
func TestAdmissionIgnoresDelete(t *testing.T) { func TestAdmissionIgnoresDelete(t *testing.T) {
namespace := "default" namespace := "default"
handler := NewResourceQuota(&testclient.Fake{}) handler := NewResourceQuota(&testclient.Fake{})
err := handler.Admit(admission.NewAttributesRecord(nil, namespace, "pods", "DELETE")) err := handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespace, "pods", "DELETE"))
if err != nil { if err != nil {
t.Errorf("ResourceQuota should admit all deletes", err) t.Errorf("ResourceQuota should admit all deletes", err)
} }
@ -67,7 +67,7 @@ func TestIncrementUsagePods(t *testing.T) {
r := api.ResourcePods r := api.ResourcePods
status.Hard[r] = resource.MustParse("2") status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, namespace, "pods", "CREATE"), status, client) dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", "CREATE"), status, client)
if err != nil { if err != nil {
t.Errorf("Unexpected error", err) t.Errorf("Unexpected error", err)
} }
@ -107,7 +107,7 @@ func TestIncrementUsageMemory(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
}} }}
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client)
if err != nil { if err != nil {
t.Errorf("Unexpected error", err) t.Errorf("Unexpected error", err)
} }
@ -148,7 +148,7 @@ func TestExceedUsageMemory(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "3Gi")}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "3Gi")}},
}} }}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) _, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client)
if err == nil { if err == nil {
t.Errorf("Expected memory usage exceeded error") t.Errorf("Expected memory usage exceeded error")
} }
@ -181,7 +181,7 @@ func TestIncrementUsageCPU(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
}} }}
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client)
if err != nil { if err != nil {
t.Errorf("Unexpected error", err) t.Errorf("Unexpected error", err)
} }
@ -222,7 +222,7 @@ func TestExceedUsageCPU(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("500m", "1Gi")}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("500m", "1Gi")}},
}} }}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) _, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client)
if err == nil { if err == nil {
t.Errorf("Expected CPU usage exceeded error") t.Errorf("Expected CPU usage exceeded error")
} }
@ -248,7 +248,7 @@ func TestExceedUsagePods(t *testing.T) {
r := api.ResourcePods r := api.ResourcePods
status.Hard[r] = resource.MustParse("1") status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, namespace, "pods", "CREATE"), status, client) _, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", "CREATE"), status, client)
if err == nil { if err == nil {
t.Errorf("Expected error because this would exceed your quota") t.Errorf("Expected error because this would exceed your quota")
} }
@ -270,7 +270,7 @@ func TestIncrementUsageServices(t *testing.T) {
r := api.ResourceServices r := api.ResourceServices
status.Hard[r] = resource.MustParse("2") status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, namespace, "services", "CREATE"), status, client) dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", "CREATE"), status, client)
if err != nil { if err != nil {
t.Errorf("Unexpected error", err) t.Errorf("Unexpected error", err)
} }
@ -299,7 +299,7 @@ func TestExceedUsageServices(t *testing.T) {
r := api.ResourceServices r := api.ResourceServices
status.Hard[r] = resource.MustParse("1") status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, namespace, "services", "CREATE"), status, client) _, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", "CREATE"), status, client)
if err == nil { if err == nil {
t.Errorf("Expected error because this would exceed usage") t.Errorf("Expected error because this would exceed usage")
} }
@ -321,7 +321,7 @@ func TestIncrementUsageReplicationControllers(t *testing.T) {
r := api.ResourceReplicationControllers r := api.ResourceReplicationControllers
status.Hard[r] = resource.MustParse("2") status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, namespace, "replicationControllers", "CREATE"), status, client) dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", "CREATE"), status, client)
if err != nil { if err != nil {
t.Errorf("Unexpected error", err) t.Errorf("Unexpected error", err)
} }
@ -350,7 +350,7 @@ func TestExceedUsageReplicationControllers(t *testing.T) {
r := api.ResourceReplicationControllers r := api.ResourceReplicationControllers
status.Hard[r] = resource.MustParse("1") status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, namespace, "replicationControllers", "CREATE"), status, client) _, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", "CREATE"), status, client)
if err == nil { if err == nil {
t.Errorf("Expected error for exceeding hard limits") t.Errorf("Expected error for exceeding hard limits")
} }