API changes for finalizers and system-wide garbage collector

This commit is contained in:
Chao Xu 2016-04-06 10:16:15 -07:00
parent 93e3df8e55
commit a46b7775fc
4 changed files with 167 additions and 0 deletions

View File

@ -138,6 +138,16 @@ type ObjectMeta struct {
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
// List of objects depended by this object. If ALL objects in the list have
// been deleted, this object will be garbage collected.
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty"`
// Must be empty before the object is deleted from the registry. Each entry
// is an identifier for the responsible component that will remove the entry
// from the list. If the deletionTimestamp of the object is non-nil, entries
// in this list can only be removed.
Finalizers []string `json:"finalizers,omitempty"`
}
const (
@ -1925,6 +1935,10 @@ type DeleteOptions struct {
// Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be
// returned.
Preconditions *Preconditions `json:"preconditions,omitempty"`
// Should the dependent objects be orphaned. If true/false, the "orphan"
// finalizer will be added to/removed from the object's finalizers list.
OrphanDependents *bool `json:"orphanDependents,omitempty"`
}
// ExportOptions is the query options to the standard REST get call.
@ -2057,6 +2071,23 @@ type ServiceProxyOptions struct {
Path string
}
// OwnerReference contains enough information to let you identify an owning
// object. Currently, an owning object must be in the same namespace, so there
// is no namespace field.
type OwnerReference struct {
// API version of the referent.
APIVersion string `json:"apiVersion"`
// Kind of the referent.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind"`
// Name of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names
Name string `json:"name"`
// UID of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids
UID types.UID `json:"uid"`
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`

View File

@ -175,6 +175,16 @@ type ObjectMeta struct {
// queryable and should be preserved when modifying objects.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/annotations.md
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
// List of objects depended by this object. If ALL objects in the list have
// been deleted, this object will be garbage collected.
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid"`
// Must be empty before the object is deleted from the registry. Each entry
// is an identifier for the responsible component that will remove the entry
// from the list. If the deletionTimestamp of the object is non-nil, entries
// in this list can only be removed.
Finalizers []string `json:"finalizers,omitempty"`
}
const (
@ -2331,6 +2341,10 @@ type DeleteOptions struct {
// Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be
// returned.
Preconditions *Preconditions `json:"preconditions,omitempty" protobuf:"bytes,2,opt,name=preconditions"`
// Should the dependent objects be orphaned. If true/false, the "orphan"
// finalizer will be added to/removed from the object's finalizers list.
OrphanDependents *bool `json:"orphanDependents,omitempty"`
}
// ExportOptions is the query options to the standard REST get call.
@ -2484,6 +2498,23 @@ type ServiceProxyOptions struct {
Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"`
}
// OwnerReference contains enough information to let you identify an owning
// object. Currently, an owning object must be in the same namespace, so there
// is no namespace field.
type OwnerReference struct {
// API version of the referent.
APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"`
// Kind of the referent.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"`
// Name of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names
Name string `json:"name" protobuf:"bytes,3,opt,name=name"`
// UID of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids
UID types.UID `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
// Kind of the referent.

View File

@ -33,6 +33,8 @@ import (
utilpod "k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/api/resource"
apiservice "k8s.io/kubernetes/pkg/api/service"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/intstr"
@ -67,6 +69,11 @@ var PortNameErrorMsg string = fmt.Sprintf(`must be an IANA_SVC_NAME (at most 15
const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
// BannedOwners is a black list of object that are not allowed to be owners.
var BannedOwners = map[unversioned.GroupVersionKind]struct{}{
v1.SchemeGroupVersion.WithKind("Event"): {},
}
func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if !validation.IsQualifiedName(labelName) {
@ -145,6 +152,36 @@ func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath
return allErrs
}
func validateOwnerReference(ownerReference api.OwnerReference, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
gvk := unversioned.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind)
// gvk.Group is empty for the legacy group.
if len(gvk.Version) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty"))
}
if len(gvk.Kind) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty"))
}
if len(ownerReference.Name) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty"))
}
if len(ownerReference.UID) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty"))
}
if _, ok := BannedOwners[*gvk]; ok {
allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk)))
}
return allErrs
}
func ValidateOwnerReferences(ownerReferences []api.OwnerReference, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, ref := range ownerReferences {
allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
}
return allErrs
}
// ValidateNameFunc validates that the provided name is valid for a given resource type.
// Not all resources have the same validation rules for names. Prefix is true if the
// name will have a value appended to it.
@ -326,6 +363,7 @@ func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn Val
allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...)
allErrs = append(allErrs, ValidateLabels(meta.Labels, fldPath.Child("labels"))...)
allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...)
return allErrs
}
@ -376,9 +414,11 @@ func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.P
allErrs = append(allErrs, ValidateImmutableField(newMeta.Namespace, oldMeta.Namespace, fldPath.Child("namespace"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.UID, oldMeta.UID, fldPath.Child("uid"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...)
allErrs = append(allErrs, ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...)
allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...)
return allErrs
}

View File

@ -98,6 +98,56 @@ func TestValidateObjectMetaNamespaces(t *testing.T) {
}
}
func TestValidateObjectMetaOwnerReferences(t *testing.T) {
testCases := []struct {
ownerReferences []api.OwnerReference
expectError bool
}{
{
[]api.OwnerReference{
{
APIVersion: "thirdpartyVersion",
Kind: "thirdpartyKind",
Name: "name",
UID: "1",
},
},
false,
},
{
// event shouldn't be set as an owner
[]api.OwnerReference{
{
APIVersion: "v1",
Kind: "Event",
Name: "name",
UID: "1",
},
},
true,
},
}
for _, tc := range testCases {
errs := ValidateObjectMeta(
&api.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences},
true,
func(s string, prefix bool) (bool, string) {
return true, ""
},
field.NewPath("field"))
if len(errs) != 0 && !tc.expectError {
t.Errorf("unexpected error: %v", errs)
}
if len(errs) == 0 && tc.expectError {
t.Errorf("expect error")
}
if len(errs) != 0 && !strings.Contains(errs[0].Error(), "is disallowed from being an owner") {
t.Errorf("unexpected error message: %v", errs)
}
}
}
func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
if errs := ValidateObjectMetaUpdate(
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
@ -217,6 +267,21 @@ func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) {
}
}
// Ensure updating finalizers is disallowed
func TestValidateObjectMetaUpdateDisallowsUpdatingFinalizers(t *testing.T) {
errs := ValidateObjectMetaUpdate(
&api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"orphaning"}},
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
field.NewPath("field"),
)
if len(errs) != 1 {
t.Fatalf("unexpected errors: %v", errs)
}
if !strings.Contains(errs[0].Error(), "field is immutable") {
t.Errorf("unexpected error message: %v", errs)
}
}
func TestValidateLabels(t *testing.T) {
successCases := []map[string]string{
{"simple": "bar"},