Deprecate and remove use of alpha metadata.initializers field, remove IncludeUninitialized options

This commit is contained in:
Jordan Liggitt 2019-01-17 09:19:28 -05:00
parent dc1fa870bf
commit 17aa60686e
77 changed files with 236 additions and 3868 deletions

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,6 @@
"k8s.io/apiserver/pkg/storage", "k8s.io/apiserver/pkg/storage",
"k8s.io/api/batch/v2alpha1", "k8s.io/api/batch/v2alpha1",
"k8s.io/apiserver/pkg/registry/rest", "k8s.io/apiserver/pkg/registry/rest",
"k8s.io/apimachinery/pkg/util/initialization",
"k8s.io/api/scheduling/v1alpha1", "k8s.io/api/scheduling/v1alpha1",
"k8s.io/api/admissionregistration/v1beta1", "k8s.io/api/admissionregistration/v1beta1",
"k8s.io/api/authorization/v1", "k8s.io/api/authorization/v1",
@ -313,7 +312,6 @@
"k8s.io/kubernetes/pkg/util/net/sets", "k8s.io/kubernetes/pkg/util/net/sets",
"k8s.io/kubernetes/pkg/util/parsers", "k8s.io/kubernetes/pkg/util/parsers",
"k8s.io/kubernetes/pkg/fieldpath", "k8s.io/kubernetes/pkg/fieldpath",
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util",
"k8s.io/kubernetes/pkg/scheduler/volumebinder", "k8s.io/kubernetes/pkg/scheduler/volumebinder",
"k8s.io/kubernetes/pkg/scheduler/internal/cache", "k8s.io/kubernetes/pkg/scheduler/internal/cache",
"k8s.io/kubernetes/pkg/util/nsenter", "k8s.io/kubernetes/pkg/util/nsenter",

View File

@ -378,7 +378,7 @@ func (d *namespacedResourcesDeleter) listCollection(gvr schema.GroupVersionResou
return nil, false, nil return nil, false, nil
} }
unstructuredList, err := d.dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{IncludeUninitialized: true}) unstructuredList, err := d.dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{})
if err == nil { if err == nil {
return unstructuredList, true, nil return unstructuredList, true, nil
} }
@ -543,7 +543,7 @@ func (d *namespacedResourcesDeleter) estimateGracefulTerminationForPods(ns strin
if podsGetter == nil || reflect.ValueOf(podsGetter).IsNil() { if podsGetter == nil || reflect.ValueOf(podsGetter).IsNil() {
return estimate, fmt.Errorf("unexpected: podsGetter is nil. Cannot estimate grace period seconds for pods") return estimate, fmt.Errorf("unexpected: podsGetter is nil. Cannot estimate grace period seconds for pods")
} }
items, err := podsGetter.Pods(ns).List(metav1.ListOptions{IncludeUninitialized: true}) items, err := podsGetter.Pods(ns).List(metav1.ListOptions{})
if err != nil { if err != nil {
return estimate, err return estimate, err
} }

View File

@ -485,7 +485,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
genericfeatures.AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA}, genericfeatures.AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA},
genericfeatures.DynamicAuditing: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.DynamicAuditing: {Default: false, PreRelease: utilfeature.Alpha},
genericfeatures.APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha},
genericfeatures.Initializers: {Default: false, PreRelease: utilfeature.Alpha},
genericfeatures.APIListChunking: {Default: true, PreRelease: utilfeature.Beta}, genericfeatures.APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
genericfeatures.DryRun: {Default: true, PreRelease: utilfeature.Beta}, genericfeatures.DryRun: {Default: true, PreRelease: utilfeature.Beta},

View File

@ -1,30 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["initializer.go"],
importpath = "k8s.io/kubernetes/pkg/kubeapiserver/admission/util",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/initialization:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,79 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
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 util
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/util/initialization"
"k8s.io/apiserver/pkg/admission"
)
// IsUpdatingInitializedObject returns true if the operation is trying to update
// an already initialized object.
func IsUpdatingInitializedObject(a admission.Attributes) (bool, error) {
if a.GetOperation() != admission.Update {
return false, nil
}
oldObj := a.GetOldObject()
accessor, err := meta.Accessor(oldObj)
if err != nil {
return false, err
}
if initialization.IsInitialized(accessor.GetInitializers()) {
return true, nil
}
return false, nil
}
// IsUpdatingUninitializedObject returns true if the operation is trying to
// update an object that is not initialized yet.
func IsUpdatingUninitializedObject(a admission.Attributes) (bool, error) {
if a.GetOperation() != admission.Update {
return false, nil
}
oldObj := a.GetOldObject()
accessor, err := meta.Accessor(oldObj)
if err != nil {
return false, err
}
if initialization.IsInitialized(accessor.GetInitializers()) {
return false, nil
}
return true, nil
}
// IsInitializationCompletion returns true if the operation removes all pending
// initializers.
func IsInitializationCompletion(a admission.Attributes) (bool, error) {
if a.GetOperation() != admission.Update {
return false, nil
}
oldObj := a.GetOldObject()
oldInitialized, err := initialization.IsObjectInitialized(oldObj)
if err != nil {
return false, err
}
if oldInitialized {
return false, nil
}
newObj := a.GetObject()
newInitialized, err := initialization.IsObjectInitialized(newObj)
if err != nil {
return false, err
}
return newInitialized, nil
}

View File

@ -69,7 +69,6 @@ type AnnotateOptions struct {
enforceNamespace bool enforceNamespace bool
builder *resource.Builder builder *resource.Builder
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
includeUninitialized bool
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -183,7 +182,6 @@ func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if err != nil { if err != nil {
return err return err
} }
o.includeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder() o.builder = f.NewBuilder()
o.unstructuredClientForMapping = f.UnstructuredClientForMapping o.unstructuredClientForMapping = f.UnstructuredClientForMapping
@ -227,7 +225,6 @@ func (o AnnotateOptions) RunAnnotate() error {
ContinueOnError(). ContinueOnError().
NamespaceParam(o.namespace).DefaultNamespace(). NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions). FilenameParam(o.enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(o.includeUninitialized).
Flatten() Flatten()
if !o.local { if !o.local {

View File

@ -65,17 +65,16 @@ type ApplyOptions struct {
DeleteFlags *delete.DeleteFlags DeleteFlags *delete.DeleteFlags
DeleteOptions *delete.DeleteOptions DeleteOptions *delete.DeleteOptions
Selector string Selector string
DryRun bool DryRun bool
ServerDryRun bool ServerDryRun bool
Prune bool Prune bool
PruneResources []pruneResource PruneResources []pruneResource
cmdBaseName string cmdBaseName string
All bool All bool
Overwrite bool Overwrite bool
OpenAPIPatch bool OpenAPIPatch bool
PruneWhitelist []string PruneWhitelist []string
ShouldIncludeUninitialized bool
Validator validation.Schema Validator validation.Schema
Builder *resource.Builder Builder *resource.Builder
@ -224,7 +223,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err return err
} }
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams) o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
o.OpenAPISchema, _ = f.OpenAPISchema() o.OpenAPISchema, _ = f.OpenAPISchema()
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate")) o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
@ -315,7 +313,6 @@ func (o *ApplyOptions) Run() error {
NamespaceParam(o.Namespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions). FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
LabelSelectorParam(o.Selector). LabelSelectorParam(o.Selector).
IncludeUninitialized(o.ShouldIncludeUninitialized).
Flatten(). Flatten().
Do() Do()
if err := r.Err(); err != nil { if err := r.Err(); err != nil {
@ -535,13 +532,13 @@ func (o *ApplyOptions) Run() error {
for n := range visitedNamespaces { for n := range visitedNamespaces {
for _, m := range namespacedRESTMappings { for _, m := range namespacedRESTMappings {
if err := p.prune(n, m, o.ShouldIncludeUninitialized); err != nil { if err := p.prune(n, m); err != nil {
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err) return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
} }
} }
} }
for _, m := range nonNamespacedRESTMappings { for _, m := range nonNamespacedRESTMappings {
if err := p.prune(metav1.NamespaceNone, m, o.ShouldIncludeUninitialized); err != nil { if err := p.prune(metav1.NamespaceNone, m); err != nil {
return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err) return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
} }
} }
@ -617,13 +614,12 @@ type pruner struct {
out io.Writer out io.Writer
} }
func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, includeUninitialized bool) error { func (p *pruner) prune(namespace string, mapping *meta.RESTMapping) error {
objList, err := p.dynamicClient.Resource(mapping.Resource). objList, err := p.dynamicClient.Resource(mapping.Resource).
Namespace(namespace). Namespace(namespace).
List(metav1.ListOptions{ List(metav1.ListOptions{
LabelSelector: p.labelSelector, LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector, FieldSelector: p.fieldSelector,
IncludeUninitialized: includeUninitialized,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -162,7 +162,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
o.GracePeriod = 1 o.GracePeriod = 1
} }
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := f.NewBuilder(). r := f.NewBuilder().
Unstructured(). Unstructured().
ContinueOnError(). ContinueOnError().
@ -170,7 +169,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
LabelSelectorParam(o.LabelSelector). LabelSelectorParam(o.LabelSelector).
FieldSelectorParam(o.FieldSelector). FieldSelectorParam(o.FieldSelector).
IncludeUninitialized(includeUninitialized).
SelectAllParam(o.DeleteAll). SelectAllParam(o.DeleteAll).
ResourceTypeOrNameArgs(false, args...).RequireObject(false). ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten(). Flatten().

View File

@ -79,9 +79,8 @@ type DescribeOptions struct {
BuilderArgs []string BuilderArgs []string
EnforceNamespace bool EnforceNamespace bool
AllNamespaces bool AllNamespaces bool
IncludeUninitialized bool
DescriberSettings *describe.DescriberSettings DescriberSettings *describe.DescriberSettings
FilenameOptions *resource.FilenameOptions FilenameOptions *resource.FilenameOptions
@ -144,9 +143,6 @@ func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
o.NewBuilder = f.NewBuilder o.NewBuilder = f.NewBuilder
// include the uninitialized objects by default
// unless user explicitly set --include-uninitialized=false
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, true)
return nil return nil
} }
@ -161,7 +157,6 @@ func (o *DescribeOptions) Run() error {
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
FilenameParam(o.EnforceNamespace, o.FilenameOptions). FilenameParam(o.EnforceNamespace, o.FilenameOptions).
LabelSelectorParam(o.Selector). LabelSelectorParam(o.Selector).
IncludeUninitialized(o.IncludeUninitialized).
ResourceTypeOrNameArgs(true, o.BuilderArgs...). ResourceTypeOrNameArgs(true, o.BuilderArgs...).
Flatten(). Flatten().
Do() Do()

View File

@ -79,8 +79,6 @@ type GetOptions struct {
IgnoreNotFound bool IgnoreNotFound bool
Export bool Export bool
IncludeUninitialized bool
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -224,8 +222,6 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.IsHumanReadablePrinter = true o.IsHumanReadablePrinter = true
} }
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) { o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
// make a new copy of current flags / opts before mutating // make a new copy of current flags / opts before mutating
printFlags := o.PrintFlags.Copy() printFlags := o.PrintFlags.Copy()
@ -256,9 +252,6 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
switch { switch {
case o.Watch || o.WatchOnly: case o.Watch || o.WatchOnly:
// include uninitialized objects when watching on a single object
// unless explicitly set --include-uninitialized=false
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2)
default: default:
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent)) fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent))
@ -428,7 +421,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
FieldSelectorParam(o.FieldSelector). FieldSelectorParam(o.FieldSelector).
ExportParam(o.Export). ExportParam(o.Export).
RequestChunksOf(chunkSize). RequestChunksOf(chunkSize).
IncludeUninitialized(o.IncludeUninitialized).
ResourceTypeOrNameArgs(true, args...). ResourceTypeOrNameArgs(true, args...).
ContinueOnError(). ContinueOnError().
Latest(). Latest().
@ -613,7 +605,6 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
FieldSelectorParam(o.FieldSelector). FieldSelectorParam(o.FieldSelector).
ExportParam(o.Export). ExportParam(o.Export).
RequestChunksOf(o.ChunkSize). RequestChunksOf(o.ChunkSize).
IncludeUninitialized(o.IncludeUninitialized).
ResourceTypeOrNameArgs(true, args...). ResourceTypeOrNameArgs(true, args...).
SingleResourceType(). SingleResourceType().
Latest(). Latest().

View File

@ -72,7 +72,6 @@ type LabelOptions struct {
namespace string namespace string
enforceNamespace bool enforceNamespace bool
includeUninitialized bool
builder *resource.Builder builder *resource.Builder
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
@ -192,7 +191,6 @@ func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if err != nil { if err != nil {
return err return err
} }
o.includeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder() o.builder = f.NewBuilder()
o.unstructuredClientForMapping = f.UnstructuredClientForMapping o.unstructuredClientForMapping = f.UnstructuredClientForMapping
@ -224,7 +222,6 @@ func (o *LabelOptions) RunLabel() error {
ContinueOnError(). ContinueOnError().
NamespaceParam(o.namespace).DefaultNamespace(). NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions). FilenameParam(o.enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(o.includeUninitialized).
Flatten() Flatten()
if !o.local { if !o.local {

View File

@ -163,14 +163,12 @@ func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
return err return err
} }
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local). LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten() Flatten()
if !o.Local { if !o.Local {

View File

@ -167,14 +167,12 @@ func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar
return err return err
} }
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local). LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten() Flatten()
if !o.Local { if !o.Local {

View File

@ -80,7 +80,6 @@ func NewSelectorOptions(streams genericclioptions.IOStreams) *SetSelectorOptions
WithScheme(scheme.Scheme). WithScheme(scheme.Scheme).
WithAll(false). WithAll(false).
WithLocal(false). WithLocal(false).
WithUninitialized(false).
WithLatest(), WithLatest(),
PrintFlags: genericclioptions.NewPrintFlags("selector updated").WithTypeSetter(scheme.Scheme), PrintFlags: genericclioptions.NewPrintFlags("selector updated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),

View File

@ -151,14 +151,12 @@ func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Comman
} }
o.serviceAccountName = args[len(args)-1] o.serviceAccountName = args[len(args)-1]
resources := args[:len(args)-1] resources := args[:len(args)-1]
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.local). LocalParam(o.local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.fileNameOptions). FilenameParam(enforceNamespace, &o.fileNameOptions).
IncludeUninitialized(includeUninitialized).
Flatten() Flatten()
if !o.local { if !o.local {
builder.ResourceTypeOrNameArgs(o.all, resources...). builder.ResourceTypeOrNameArgs(o.all, resources...).

View File

@ -138,14 +138,12 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return err return err
} }
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
LocalParam(o.Local). LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(o.namespace).DefaultNamespace(). NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten() Flatten()
if o.Local { if o.Local {

View File

@ -177,10 +177,8 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm
// when do normal edit or apply edit we need to always retrieve the latest resource from server // when do normal edit or apply edit we need to always retrieve the latest resource from server
b = b.ResourceTypeOrNameArgs(true, args...).Latest() b = b.ResourceTypeOrNameArgs(true, args...).Latest()
} }
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := b.NamespaceParam(cmdNamespace).DefaultNamespace(). r := b.NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
ContinueOnError(). ContinueOnError().
Flatten(). Flatten().
Do() Do()
@ -195,7 +193,6 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm
return f.NewBuilder(). return f.NewBuilder().
Unstructured(). Unstructured().
Stream(bytes.NewReader(data), "edited-file"). Stream(bytes.NewReader(data), "edited-file").
IncludeUninitialized(includeUninitialized).
ContinueOnError(). ContinueOnError().
Flatten(). Flatten().
Do() Do()

View File

@ -48,9 +48,8 @@ import (
) )
const ( const (
ApplyAnnotationsFlag = "save-config" ApplyAnnotationsFlag = "save-config"
DefaultErrorExitCode = 1 DefaultErrorExitCode = 1
IncludeUninitializedFlag = "include-uninitialized"
) )
type debugError interface { type debugError interface {
@ -402,7 +401,8 @@ func AddDryRunFlag(cmd *cobra.Command) {
} }
func AddIncludeUninitializedFlag(cmd *cobra.Command) { func AddIncludeUninitializedFlag(cmd *cobra.Command) {
cmd.Flags().Bool(IncludeUninitializedFlag, false, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`) cmd.Flags().Bool("include-uninitialized", false, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`)
cmd.Flags().MarkDeprecated("include-uninitialized", "The Initializers feature has been removed. This flag is now a no-op, and will be removed in v1.15")
} }
func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) { func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) {
@ -594,28 +594,6 @@ func ManualStrip(file []byte) []byte {
return stripped return stripped
} }
// ShouldIncludeUninitialized identifies whether to include uninitialized objects.
// includeUninitialized is the default value.
// Assume we can parse `all` and `selector` from cmd.
func ShouldIncludeUninitialized(cmd *cobra.Command, includeUninitialized bool) bool {
shouldIncludeUninitialized := includeUninitialized
if cmd.Flags().Lookup("all") != nil && GetFlagBool(cmd, "all") {
// include the uninitialized objects by default
// unless explicitly set --include-uninitialized=false
shouldIncludeUninitialized = true
}
if cmd.Flags().Lookup("selector") != nil && GetFlagString(cmd, "selector") != "" {
// does not include the uninitialized objects by default
// unless explicitly set --include-uninitialized=true
shouldIncludeUninitialized = false
}
if cmd.Flags().Changed(IncludeUninitializedFlag) {
// get explicit value
shouldIncludeUninitialized = GetFlagBool(cmd, IncludeUninitializedFlag)
}
return shouldIncludeUninitialized
}
// ScaleClientFunc provides a ScalesGetter // ScaleClientFunc provides a ScalesGetter
type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error) type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error)

View File

@ -24,16 +24,12 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/initialization"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/apis/core/v1/helper"
k8sfeatures "k8s.io/kubernetes/pkg/features" k8sfeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
quota "k8s.io/kubernetes/pkg/quota/v1" quota "k8s.io/kubernetes/pkg/quota/v1"
"k8s.io/kubernetes/pkg/quota/v1/generic" "k8s.io/kubernetes/pkg/quota/v1/generic"
) )
@ -99,25 +95,9 @@ func (p *pvcEvaluator) Handles(a admission.Attributes) bool {
return true return true
} }
if op == admission.Update && utilfeature.DefaultFeatureGate.Enabled(k8sfeatures.ExpandPersistentVolumes) { if op == admission.Update && utilfeature.DefaultFeatureGate.Enabled(k8sfeatures.ExpandPersistentVolumes) {
initialized, err := initialization.IsObjectInitialized(a.GetObject())
if err != nil {
// fail closed, will try to give an evaluation.
utilruntime.HandleError(err)
return true
}
// only handle the update if the object is initialized after the update.
return initialized
}
// TODO: when the ExpandPersistentVolumes feature gate is removed, remove
// the initializationCompletion check as well, because it will become a
// subset of the "initialized" condition.
initializationCompletion, err := util.IsInitializationCompletion(a)
if err != nil {
// fail closed, will try to give an evaluation.
utilruntime.HandleError(err)
return true return true
} }
return initializationCompletion return false
} }
// Matches returns true if the evaluator matches the specified quota with the provided input item // Matches returns true if the evaluator matches the specified quota with the provided input item
@ -173,12 +153,6 @@ func (p *pvcEvaluator) Usage(item runtime.Object) (corev1.ResourceList, error) {
// charge for claim // charge for claim
result[corev1.ResourcePersistentVolumeClaims] = *(resource.NewQuantity(1, resource.DecimalSI)) result[corev1.ResourcePersistentVolumeClaims] = *(resource.NewQuantity(1, resource.DecimalSI))
result[pvcObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) result[pvcObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI))
if utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
if !initialization.IsInitialized(pvc.Initializers) {
// Only charge pvc count for uninitialized pvc.
return result, nil
}
}
storageClassRef := helper.GetPersistentVolumeClaimClass(pvc) storageClassRef := helper.GetPersistentVolumeClaimClass(pvc)
if len(storageClassRef) > 0 { if len(storageClassRef) > 0 {
storageClassClaim := corev1.ResourceName(storageClassRef + storageClassSuffix + string(corev1.ResourcePersistentVolumeClaims)) storageClassClaim := corev1.ResourceName(storageClassRef + storageClassSuffix + string(corev1.ResourcePersistentVolumeClaims))

View File

@ -28,14 +28,12 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
quota "k8s.io/kubernetes/pkg/quota/v1" quota "k8s.io/kubernetes/pkg/quota/v1"
"k8s.io/kubernetes/pkg/quota/v1/generic" "k8s.io/kubernetes/pkg/quota/v1/generic"
) )
@ -150,14 +148,7 @@ func (p *podEvaluator) Handles(a admission.Attributes) bool {
if op == admission.Create { if op == admission.Create {
return true return true
} }
initializationCompletion, err := util.IsInitializationCompletion(a) return false
if err != nil {
// fail closed, will try to give an evaluation.
utilruntime.HandleError(err)
return true
}
// only uninitialized pods might be updated.
return initializationCompletion
} }
// Matches returns true if the evaluator matches the specified quota with the provided input item // Matches returns true if the evaluator matches the specified quota with the provided input item

View File

@ -160,12 +160,12 @@ func ReplicaSetToSelectableFields(rs *apps.ReplicaSet) fields.Set {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
rs, ok := obj.(*apps.ReplicaSet) rs, ok := obj.(*apps.ReplicaSet)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a ReplicaSet.") return nil, nil, fmt.Errorf("given object is not a ReplicaSet.")
} }
return labels.Set(rs.ObjectMeta.Labels), ReplicaSetToSelectableFields(rs), rs.Initializers != nil, nil return labels.Set(rs.ObjectMeta.Labels), ReplicaSetToSelectableFields(rs), nil
} }
// MatchReplicaSet is the filter used by the generic etcd backend to route // MatchReplicaSet is the filter used by the generic etcd backend to route

View File

@ -202,12 +202,12 @@ func JobToSelectableFields(job *batch.Job) fields.Set {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
job, ok := obj.(*batch.Job) job, ok := obj.(*batch.Job)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a job.") return nil, nil, fmt.Errorf("given object is not a job.")
} }
return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), job.Initializers != nil, nil return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), nil
} }
// MatchJob is the filter used by the generic etcd backend to route // MatchJob is the filter used by the generic etcd backend to route

View File

@ -79,12 +79,12 @@ func (eventStrategy) AllowUnconditionalUpdate() bool {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
event, ok := obj.(*api.Event) event, ok := obj.(*api.Event)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not an event") return nil, nil, fmt.Errorf("not an event")
} }
return labels.Set(event.Labels), EventToSelectableFields(event), event.Initializers != nil, nil return labels.Set(event.Labels), EventToSelectableFields(event), nil
} }
func MatchEvent(label labels.Selector, field fields.Selector) storage.SelectionPredicate { func MatchEvent(label labels.Selector, field fields.Selector) storage.SelectionPredicate {

View File

@ -139,12 +139,12 @@ func (namespaceFinalizeStrategy) PrepareForUpdate(ctx context.Context, obj, old
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
namespaceObj, ok := obj.(*api.Namespace) namespaceObj, ok := obj.(*api.Namespace)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a namespace") return nil, nil, fmt.Errorf("not a namespace")
} }
return labels.Set(namespaceObj.Labels), NamespaceToSelectableFields(namespaceObj), namespaceObj.Initializers != nil, nil return labels.Set(namespaceObj.Labels), NamespaceToSelectableFields(namespaceObj), nil
} }
// MatchNamespace returns a generic matcher for a given label and field selector. // MatchNamespace returns a generic matcher for a given label and field selector.

View File

@ -181,12 +181,12 @@ func NodeToSelectableFields(node *api.Node) fields.Set {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
nodeObj, ok := obj.(*api.Node) nodeObj, ok := obj.(*api.Node)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a node") return nil, nil, fmt.Errorf("not a node")
} }
return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nodeObj.Initializers != nil, nil return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nil
} }
// MatchNode returns a generic matcher for a given label and field selector. // MatchNode returns a generic matcher for a given label and field selector.

View File

@ -108,12 +108,12 @@ func (persistentvolumeStatusStrategy) ValidateUpdate(ctx context.Context, obj, o
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
persistentvolumeObj, ok := obj.(*api.PersistentVolume) persistentvolumeObj, ok := obj.(*api.PersistentVolume)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a persistentvolume") return nil, nil, fmt.Errorf("not a persistentvolume")
} }
return labels.Set(persistentvolumeObj.Labels), PersistentVolumeToSelectableFields(persistentvolumeObj), persistentvolumeObj.Initializers != nil, nil return labels.Set(persistentvolumeObj.Labels), PersistentVolumeToSelectableFields(persistentvolumeObj), nil
} }
// MatchPersistentVolume returns a generic matcher for a given label and field selector. // MatchPersistentVolume returns a generic matcher for a given label and field selector.

View File

@ -109,12 +109,12 @@ func (persistentvolumeclaimStatusStrategy) ValidateUpdate(ctx context.Context, o
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
persistentvolumeclaimObj, ok := obj.(*api.PersistentVolumeClaim) persistentvolumeclaimObj, ok := obj.(*api.PersistentVolumeClaim)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a persistentvolumeclaim") return nil, nil, fmt.Errorf("not a persistentvolumeclaim")
} }
return labels.Set(persistentvolumeclaimObj.Labels), PersistentVolumeClaimToSelectableFields(persistentvolumeclaimObj), persistentvolumeclaimObj.Initializers != nil, nil return labels.Set(persistentvolumeclaimObj.Labels), PersistentVolumeClaimToSelectableFields(persistentvolumeclaimObj), nil
} }
// MatchPersistentVolumeClaim returns a generic matcher for a given label and field selector. // MatchPersistentVolumeClaim returns a generic matcher for a given label and field selector.

View File

@ -32,7 +32,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
@ -40,8 +39,6 @@ import (
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors" storeerr "k8s.io/apiserver/pkg/storage/errors"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/securitycontext" "k8s.io/kubernetes/pkg/securitycontext"
@ -729,76 +726,6 @@ func TestEtcdCreateBinding(t *testing.T) {
} }
} }
func TestEtcdUpdateUninitialized(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
storage, _, _, server := newStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
ctx := genericapirequest.NewDefaultContext()
pod := validNewPod()
// add pending initializers to the pod
pod.ObjectMeta.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}}
if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
podIn := *pod
// only uninitialized pod is allowed to add containers via update
podIn.Spec.Containers = append(podIn.Spec.Containers, api.Container{
Name: "foo2",
Image: "test",
ImagePullPolicy: api.PullAlways,
TerminationMessagePath: api.TerminationMessagePathDefault,
TerminationMessagePolicy: api.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(),
})
podIn.ObjectMeta.Initializers = nil
_, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
obj, err := storage.Get(ctx, podIn.ObjectMeta.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
podOut := obj.(*api.Pod)
if podOut.GetInitializers() != nil {
t.Errorf("expect nil initializers, got %v", podOut.ObjectMeta.Initializers)
}
if !apiequality.Semantic.DeepEqual(podIn.Spec.Containers, podOut.Spec.Containers) {
t.Errorf("objects differ: %v", diff.ObjectDiff(podOut, &podIn))
}
}
func TestEtcdStatusUpdateUninitialized(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
storage, _, statusStorage, server := newStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
ctx := genericapirequest.NewDefaultContext()
pod := validNewPod()
// add pending initializers to the pod
pod.ObjectMeta.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}}
if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
podIn := *pod
// only uninitialized pod is allowed to add containers via update
podIn.Status.Phase = api.PodRunning
podIn.ObjectMeta.Initializers = nil
_, _, err := statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
expected := "Forbidden: must not update status when the object is uninitialized"
if err == nil {
t.Fatalf("Unexpected no err, expected %q", expected)
}
if !strings.Contains(err.Error(), expected) {
t.Errorf("unexpected error: %v, expected %q", err, expected)
}
}
func TestEtcdUpdateNotScheduled(t *testing.T) { func TestEtcdUpdateNotScheduled(t *testing.T) {
storage, _, _, server := newStorage(t) storage, _, _, server := newStorage(t)
defer server.Terminate(t) defer server.Terminate(t)

View File

@ -27,20 +27,16 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apimachineryvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
podutil "k8s.io/kubernetes/pkg/api/pod" podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
@ -100,31 +96,9 @@ func (podStrategy) AllowCreateOnUpdate() bool {
return false return false
} }
func isUpdatingUninitializedPod(old runtime.Object) (bool, error) {
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
return false, nil
}
oldMeta, err := meta.Accessor(old)
if err != nil {
return false, err
}
oldInitializers := oldMeta.GetInitializers()
if oldInitializers != nil && len(oldInitializers.Pending) != 0 {
return true, nil
}
return false, nil
}
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
errorList := validation.ValidatePod(obj.(*api.Pod)) errorList := validation.ValidatePod(obj.(*api.Pod))
uninitializedUpdate, err := isUpdatingUninitializedPod(old)
if err != nil {
return append(errorList, field.InternalError(field.NewPath("metadata"), err))
}
if uninitializedUpdate {
return errorList
}
return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...) return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
} }
@ -193,25 +167,16 @@ func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
} }
func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
var errorList field.ErrorList
uninitializedUpdate, err := isUpdatingUninitializedPod(old)
if err != nil {
return append(errorList, field.InternalError(field.NewPath("metadata"), err))
}
if uninitializedUpdate {
return append(errorList, field.Forbidden(field.NewPath("status"), apimachineryvalidation.UninitializedStatusUpdateErrorMsg))
}
// TODO: merge valid fields after update
return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod)) return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
pod, ok := obj.(*api.Pod) pod, ok := obj.(*api.Pod)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a pod") return nil, nil, fmt.Errorf("not a pod")
} }
return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), pod.Initializers != nil, nil return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil
} }
// MatchPod returns a generic matcher for a given label and field selector. // MatchPod returns a generic matcher for a given label and field selector.

View File

@ -164,12 +164,12 @@ func ControllerToSelectableFields(controller *api.ReplicationController) fields.
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
rc, ok := obj.(*api.ReplicationController) rc, ok := obj.(*api.ReplicationController)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a replication controller.") return nil, nil, fmt.Errorf("given object is not a replication controller.")
} }
return labels.Set(rc.ObjectMeta.Labels), ControllerToSelectableFields(rc), rc.Initializers != nil, nil return labels.Set(rc.ObjectMeta.Labels), ControllerToSelectableFields(rc), nil
} }
// MatchController is the filter used by the generic etcd backend to route // MatchController is the filter used by the generic etcd backend to route

View File

@ -98,12 +98,12 @@ func (s strategy) Export(ctx context.Context, obj runtime.Object, exact bool) er
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
secret, ok := obj.(*api.Secret) secret, ok := obj.(*api.Secret)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a secret") return nil, nil, fmt.Errorf("not a secret")
} }
return labels.Set(secret.Labels), SelectableFields(secret), secret.Initializers != nil, nil return labels.Set(secret.Labels), SelectableFields(secret), nil
} }
// Matcher returns a generic matcher for a given label and field selector. // Matcher returns a generic matcher for a given label and field selector.

View File

@ -85,12 +85,12 @@ func SelectableFields(pip *api.Event) fields.Set {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
pip, ok := obj.(*api.Event) pip, ok := obj.(*api.Event)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a Event") return nil, nil, fmt.Errorf("given object is not a Event")
} }
return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), pip.Initializers != nil, nil return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), nil
} }
// Matcher is the filter used by the generic etcd backend to watch events // Matcher is the filter used by the generic etcd backend to watch events

View File

@ -34,7 +34,6 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1listers "k8s.io/client-go/listers/core/v1" corev1listers "k8s.io/client-go/listers/core/v1"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
) )
// The annotation key scheduler.alpha.kubernetes.io/node-selector is for assigning // The annotation key scheduler.alpha.kubernetes.io/node-selector is for assigning
@ -99,14 +98,6 @@ func (p *podNodeSelector) Admit(a admission.Attributes) error {
if shouldIgnore(a) { if shouldIgnore(a) {
return nil return nil
} }
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
if updateInitialized {
// node selector of an initialized pod is immutable
return nil
}
if !p.WaitForReady() { if !p.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
} }
@ -199,7 +190,7 @@ func shouldIgnore(a admission.Attributes) bool {
func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector { func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector {
return &podNodeSelector{ return &podNodeSelector{
Handler: admission.NewHandler(admission.Create, admission.Update), Handler: admission.NewHandler(admission.Create),
clusterNodeSelectors: clusterNodeSelectors, clusterNodeSelectors: clusterNodeSelectors,
} }
} }

View File

@ -53,12 +53,6 @@ func TestPodAdmission(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"}, ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
} }
oldPod := *pod
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
oldPod.Spec.NodeSelector = map[string]string{
"old": "true",
}
tests := []struct { tests := []struct {
defaultNodeSelector string defaultNodeSelector string
namespaceNodeSelector string namespaceNodeSelector string
@ -182,30 +176,13 @@ func TestPodAdmission(t *testing.T) {
} else if !test.admit && err == nil { } else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName) t.Errorf("Test: %s, expected an error", test.testName)
} }
// handles update of uninitialized pod like it's newly created.
err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil))
if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
} else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName)
}
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
}
err = handler.Validate(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil))
if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
} else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName)
}
} }
} }
func TestHandles(t *testing.T) { func TestHandles(t *testing.T) {
for op, shouldHandle := range map[admission.Operation]bool{ for op, shouldHandle := range map[admission.Operation]bool{
admission.Create: true, admission.Create: true,
admission.Update: true, admission.Update: false,
admission.Connect: false, admission.Connect: false,
admission.Delete: false, admission.Delete: false,
} { } {
@ -216,40 +193,6 @@ func TestHandles(t *testing.T) {
} }
} }
func TestIgnoreUpdatingInitializedPod(t *testing.T) {
namespaceNodeSelector := "infra=true"
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "testNamespace",
Namespace: "",
Annotations: map[string]string{"scheduler.alpha.kubernetes.io/node-selector": namespaceNodeSelector},
},
}
mockClient := fake.NewSimpleClientset(namespace)
handler, informerFactory, err := newHandlerForTest(mockClient)
if err != nil {
t.Errorf("unexpected error initializing handler: %v", err)
}
handler.SetReadyFunc(func() bool { return true })
podNodeSelector := map[string]string{"infra": "false"}
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
Spec: api.PodSpec{NodeSelector: podNodeSelector},
}
// this conflicts with podNodeSelector
err = informerFactory.Core().V1().Namespaces().Informer().GetStore().Update(namespace)
if err != nil {
t.Fatal(err)
}
// if the update of initialized pod is not ignored, an error will be returned because the pod's nodeSelector conflicts with namespace's nodeSelector.
err = handler.Admit(admission.NewAttributesRecord(pod, pod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil))
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
}
// newHandlerForTest returns the admission controller configured for testing. // newHandlerForTest returns the admission controller configured for testing.
func newHandlerForTest(c kubernetes.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) { func newHandlerForTest(c kubernetes.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)

View File

@ -35,7 +35,6 @@ import (
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos" qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" schedulerapi "k8s.io/kubernetes/pkg/scheduler/api"
"k8s.io/kubernetes/pkg/util/tolerations" "k8s.io/kubernetes/pkg/util/tolerations"
pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
@ -93,11 +92,7 @@ func (p *podTolerationsPlugin) Admit(a admission.Attributes) error {
pod := a.GetObject().(*api.Pod) pod := a.GetObject().(*api.Pod)
var finalTolerations []api.Toleration var finalTolerations []api.Toleration
updateUninitialized, err := util.IsUpdatingUninitializedObject(a) if a.GetOperation() == admission.Create {
if err != nil {
return err
}
if a.GetOperation() == admission.Create || updateUninitialized {
ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace()) ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace())
if err != nil { if err != nil {
return err return err

View File

@ -254,11 +254,6 @@ func TestPodAdmission(t *testing.T) {
pod := test.pod pod := test.pod
pod.Spec.Tolerations = test.podTolerations pod.Spec.Tolerations = test.podTolerations
// copy the original pod for tests of uninitialized pod updates.
oldPod := *pod
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
oldPod.Spec.Tolerations = []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}}
err = handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, false, nil)) err = handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, false, nil))
if test.admit && err != nil { if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err) t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
@ -270,19 +265,6 @@ func TestPodAdmission(t *testing.T) {
if test.admit && !tolerations.EqualTolerations(updatedPodTolerations, test.mergedTolerations) { if test.admit && !tolerations.EqualTolerations(updatedPodTolerations, test.mergedTolerations) {
t.Errorf("Test: %s, expected: %#v but got: %#v", test.testName, test.mergedTolerations, updatedPodTolerations) t.Errorf("Test: %s, expected: %#v but got: %#v", test.testName, test.mergedTolerations, updatedPodTolerations)
} }
// handles update of uninitialized pod like it's newly created.
err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil))
if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
} else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName)
}
updatedPodTolerations = pod.Spec.Tolerations
if test.admit && !tolerations.EqualTolerations(updatedPodTolerations, test.mergedTolerations) {
t.Errorf("Test: %s, expected: %#v but got: %#v", test.testName, test.mergedTolerations, updatedPodTolerations)
}
}) })
} }
} }

View File

@ -40,7 +40,6 @@ import (
podutil "k8s.io/kubernetes/pkg/api/pod" podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
kubefeatures "k8s.io/kubernetes/pkg/features" kubefeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
) )
@ -105,7 +104,7 @@ var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&serviceAcc
// 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers // 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers
func NewServiceAccount() *serviceAccount { func NewServiceAccount() *serviceAccount {
return &serviceAccount{ return &serviceAccount{
Handler: admission.NewHandler(admission.Create, admission.Update), Handler: admission.NewHandler(admission.Create),
// TODO: enable this once we've swept secret usage to account for adding secret references to service accounts // TODO: enable this once we've swept secret usage to account for adding secret references to service accounts
LimitSecretReferences: false, LimitSecretReferences: false,
// Auto mount service account API token secrets // Auto mount service account API token secrets
@ -153,14 +152,6 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
if shouldIgnore(a) { if shouldIgnore(a) {
return nil return nil
} }
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
if updateInitialized {
// related pod spec fields are immutable after the pod is initialized
return nil
}
pod := a.GetObject().(*api.Pod) pod := a.GetObject().(*api.Pod)
@ -202,14 +193,6 @@ func (s *serviceAccount) Validate(a admission.Attributes) (err error) {
if shouldIgnore(a) { if shouldIgnore(a) {
return nil return nil
} }
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
if updateInitialized {
// related pod spec fields are immutable after the pod is initialized
return nil
}
pod := a.GetObject().(*api.Pod) pod := a.GetObject().(*api.Pod)
@ -257,6 +240,9 @@ func shouldIgnore(a admission.Attributes) bool {
if a.GetResource().GroupResource() != api.Resource("pods") { if a.GetResource().GroupResource() != api.Resource("pods") {
return true return true
} }
if a.GetSubresource() != "" {
return true
}
obj := a.GetObject() obj := a.GetObject()
if obj == nil { if obj == nil {
return true return true

View File

@ -62,17 +62,6 @@ func TestIgnoresNonCreate(t *testing.T) {
} }
} }
func TestIgnoresUpdateOfInitializedPod(t *testing.T) {
pod := &api.Pod{}
oldPod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, oldPod, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil)
handler := NewServiceAccount()
err := handler.Admit(attrs)
if err != nil {
t.Errorf("Expected update of initialized pod allowed, got err: %v", err)
}
}
func TestIgnoresNonPodResource(t *testing.T) { func TestIgnoresNonPodResource(t *testing.T) {
pod := &api.Pod{} pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("CustomResource").WithVersion("version"), "", admission.Create, false, nil) attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("CustomResource").WithVersion("version"), "", admission.Create, false, nil)
@ -362,54 +351,6 @@ func TestAutomountsAPIToken(t *testing.T) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0])
} }
// Test ServiceAccount admission plugin applies the same changes if the
// operation is an update to an uninitialized pod.
oldPod := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
// the volumeMount in the oldPod shouldn't affect the result.
VolumeMounts: []api.VolumeMount{
{
Name: "wrong-" + tokenName,
ReadOnly: true,
MountPath: DefaultAPITokenMountPath,
},
},
},
},
},
}
// oldPod is not intialized.
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
pod = &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{},
},
},
}
attrs = admission.NewAttributesRecord(pod, oldPod, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, false, nil)
err = admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if pod.Spec.ServiceAccountName != DefaultServiceAccountName {
t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName)
}
if len(pod.Spec.Volumes) != 1 {
t.Fatalf("Expected 1 volume, got %d", len(pod.Spec.Volumes))
}
if !reflect.DeepEqual(expectedVolume, pod.Spec.Volumes[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolume, pod.Spec.Volumes[0])
}
if len(pod.Spec.Containers[0].VolumeMounts) != 1 {
t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts))
}
if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0])
}
// testing InitContainers // testing InitContainers
pod = &api.Pod{ pod = &api.Pod{
Spec: api.PodSpec{ Spec: api.PodSpec{

View File

@ -153,12 +153,12 @@ func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old run
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func (a customResourceStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func (a customResourceStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
accessor, err := meta.Accessor(obj) accessor, err := meta.Accessor(obj)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), accessor.GetInitializers() != nil, nil return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), nil
} }
// objectMetaFieldsSet returns a fields that represent the ObjectMeta. // objectMetaFieldsSet returns a fields that represent the ObjectMeta.

View File

@ -164,12 +164,12 @@ func (statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Objec
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
apiserver, ok := obj.(*apiextensions.CustomResourceDefinition) apiserver, ok := obj.(*apiextensions.CustomResourceDefinition)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a CustomResourceDefinition") return nil, nil, fmt.Errorf("given object is not a CustomResourceDefinition")
} }
return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), apiserver.Initializers != nil, nil return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), nil
} }
// MatchCustomResourceDefinition is the filter used by the generic etcd backend to watch events // MatchCustomResourceDefinition is the filter used by the generic etcd backend to watch events

View File

@ -180,36 +180,10 @@ func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, name
allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
allErrs = append(allErrs, ValidateInitializers(meta.GetInitializers(), fldPath.Child("initializers"))...)
allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...)
return allErrs return allErrs
} }
func ValidateInitializers(initializers *metav1.Initializers, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if initializers == nil {
return allErrs
}
for i, initializer := range initializers.Pending {
allErrs = append(allErrs, validation.IsFullyQualifiedName(fldPath.Child("pending").Index(i).Child("name"), initializer.Name)...)
}
allErrs = append(allErrs, validateInitializersResult(initializers.Result, fldPath.Child("result"))...)
return allErrs
}
func validateInitializersResult(result *metav1.Status, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if result == nil {
return allErrs
}
switch result.Status {
case metav1.StatusFailure:
default:
allErrs = append(allErrs, field.Invalid(fldPath.Child("status"), result.Status, "must be 'Failure'"))
}
return allErrs
}
// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. // ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers.
func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
@ -265,8 +239,6 @@ func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *f
allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
} }
allErrs = append(allErrs, ValidateInitializersUpdate(newMeta.GetInitializers(), oldMeta.GetInitializers(), fldPath.Child("initializers"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
@ -281,28 +253,3 @@ func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *f
return allErrs return allErrs
} }
// ValidateInitializersUpdate checks the update of the metadata initializers field
func ValidateInitializersUpdate(newInit, oldInit *metav1.Initializers, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
switch {
case oldInit == nil && newInit != nil:
// Initializers may not be set on new objects
allErrs = append(allErrs, field.Invalid(fldPath, nil, "field is immutable once initialization has completed"))
case oldInit != nil && newInit == nil:
// this is a valid transition and means initialization was successful
case oldInit != nil && newInit != nil:
// validate changes to initializers
switch {
case oldInit.Result == nil && newInit.Result != nil:
// setting a result is allowed
allErrs = append(allErrs, validateInitializersResult(newInit.Result, fldPath.Child("result"))...)
case oldInit.Result != nil:
// setting Result implies permanent failure, and all future updates will be prevented
allErrs = append(allErrs, ValidateImmutableField(newInit.Result, oldInit.Result, fldPath.Child("result"))...)
default:
// leaving the result nil is allowed
}
}
return allErrs
}

View File

@ -216,10 +216,7 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
} }
}, },
func(j *metav1.Initializers, c fuzz.Continue) { func(j *metav1.Initializers, c fuzz.Continue) {
c.FuzzNoCustom(j) j = nil
if len(j.Pending) == 0 {
j.Pending = nil
}
}, },
func(j *metav1.ListMeta, c fuzz.Continue) { func(j *metav1.ListMeta, c fuzz.Continue) {
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)

View File

@ -28,7 +28,6 @@ func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out
if err := metav1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { if err := metav1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil {
return err return err
} }
out.IncludeUninitialized = in.IncludeUninitialized
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.TimeoutSeconds = in.TimeoutSeconds out.TimeoutSeconds = in.TimeoutSeconds
out.Watch = in.Watch out.Watch = in.Watch
@ -44,7 +43,6 @@ func Convert_v1_ListOptions_To_internalversion_ListOptions(in *metav1.ListOption
if err := metav1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { if err := metav1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil {
return err return err
} }
out.IncludeUninitialized = in.IncludeUninitialized
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.TimeoutSeconds = in.TimeoutSeconds out.TimeoutSeconds = in.TimeoutSeconds
out.Watch = in.Watch out.Watch = in.Watch

View File

@ -33,9 +33,6 @@ type ListOptions struct {
LabelSelector labels.Selector LabelSelector labels.Selector
// A selector based on fields // A selector based on fields
FieldSelector fields.Selector FieldSelector fields.Selector
// If true, partially initialized resources are included in the response.
// +optional
IncludeUninitialized bool
// If true, watch for changes to this list // If true, watch for changes to this list
Watch bool Watch bool
// When specified with a watch call, shows changes that occur after that particular version of a resource. // When specified with a watch call, shows changes that occur after that particular version of a resource.

View File

@ -117,7 +117,6 @@ func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions,
if err := v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { if err := v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil {
return err return err
} }
out.IncludeUninitialized = in.IncludeUninitialized
out.Watch = in.Watch out.Watch = in.Watch
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))
@ -133,7 +132,6 @@ func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOption
if err := v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil { if err := v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil {
return err return err
} }
out.IncludeUninitialized = in.IncludeUninitialized
out.Watch = in.Watch out.Watch = in.Watch
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))

View File

@ -327,9 +327,9 @@ type ListOptions struct {
// Defaults to everything. // Defaults to everything.
// +optional // +optional
FieldSelector string `json:"fieldSelector,omitempty" protobuf:"bytes,2,opt,name=fieldSelector"` FieldSelector string `json:"fieldSelector,omitempty" protobuf:"bytes,2,opt,name=fieldSelector"`
// If true, partially initialized resources are included in the response.
// +optional // +k8s:deprecated=includeUninitialized,protobuf=6
IncludeUninitialized bool `json:"includeUninitialized,omitempty" protobuf:"varint,6,opt,name=includeUninitialized"`
// Watch for changes to the described resources and return them as a stream of // Watch for changes to the described resources and return them as a stream of
// add, update, and remove notifications. Specify resourceVersion. // add, update, and remove notifications. Specify resourceVersion.
// +optional // +optional
@ -402,9 +402,7 @@ type GetOptions struct {
// - if it's 0, then we simply return what we currently have in cache, no guarantee; // - if it's 0, then we simply return what we currently have in cache, no guarantee;
// - if set to non zero, then the result is at least as fresh as given rv. // - if set to non zero, then the result is at least as fresh as given rv.
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"` ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"`
// If true, partially initialized resources are included in the response. // +k8s:deprecated=includeUninitialized,protobuf=2
// +optional
IncludeUninitialized bool `json:"includeUninitialized,omitempty" protobuf:"varint,2,opt,name=includeUninitialized"`
} }
// DeletionPropagation decides if a deletion will propagate to the dependents of // DeletionPropagation decides if a deletion will propagate to the dependents of
@ -489,10 +487,7 @@ type CreateOptions struct {
// - All: all dry run stages will be processed // - All: all dry run stages will be processed
// +optional // +optional
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
// +k8s:deprecated=includeUninitialized,protobuf=2
// If IncludeUninitialized is specified, the object may be
// returned without completing initialization.
IncludeUninitialized bool `json:"includeUninitialized,omitempty" protobuf:"varint,2,opt,name=includeUninitialized"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -86,9 +86,8 @@ func (APIVersions) SwaggerDoc() map[string]string {
} }
var map_CreateOptions = map[string]string{ var map_CreateOptions = map[string]string{
"": "CreateOptions may be provided when creating an API object.", "": "CreateOptions may be provided when creating an API object.",
"dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"includeUninitialized": "If IncludeUninitialized is specified, the object may be returned without completing initialization.",
} }
func (CreateOptions) SwaggerDoc() map[string]string { func (CreateOptions) SwaggerDoc() map[string]string {
@ -119,9 +118,8 @@ func (ExportOptions) SwaggerDoc() map[string]string {
} }
var map_GetOptions = map[string]string{ var map_GetOptions = map[string]string{
"": "GetOptions is the standard query options to the standard REST get call.", "": "GetOptions is the standard query options to the standard REST get call.",
"resourceVersion": "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", "resourceVersion": "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.",
"includeUninitialized": "If true, partially initialized resources are included in the response.",
} }
func (GetOptions) SwaggerDoc() map[string]string { func (GetOptions) SwaggerDoc() map[string]string {
@ -200,15 +198,14 @@ func (ListMeta) SwaggerDoc() map[string]string {
} }
var map_ListOptions = map[string]string{ var map_ListOptions = map[string]string{
"": "ListOptions is the query options to a standard REST list call.", "": "ListOptions is the query options to a standard REST list call.",
"labelSelector": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", "labelSelector": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
"fieldSelector": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", "fieldSelector": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
"includeUninitialized": "If true, partially initialized resources are included in the response.", "watch": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
"watch": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", "resourceVersion": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.",
"resourceVersion": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", "timeoutSeconds": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
"timeoutSeconds": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
"limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", "continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
"continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
} }
func (ListOptions) SwaggerDoc() map[string]string { func (ListOptions) SwaggerDoc() map[string]string {

View File

@ -1,28 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["initialization.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/initialization",
importpath = "k8s.io/apimachinery/pkg/util/initialization",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,48 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
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 initialization
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// IsInitialized returns if the initializers indicates means initialized.
func IsInitialized(initializers *metav1.Initializers) bool {
if initializers == nil {
return true
}
// Persisted objects will never be in this state. The initializer admission
// plugin will override metadata.initializers to nil. If the initializer
// admissio plugin is disabled, the generic registry always set
// metadata.initializers to nil. However, this function
// might be called before the object persisted, thus the check.
if len(initializers.Pending) == 0 && initializers.Result == nil {
return true
}
return false
}
// IsObjectInitialized returns if the object is initialized.
func IsObjectInitialized(obj runtime.Object) (bool, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return false, err
}
return IsInitialized(accessor.GetInitializers()), nil
}

View File

@ -335,7 +335,6 @@ type SimpleRESTStorage struct {
fakeWatch *watch.FakeWatcher fakeWatch *watch.FakeWatcher
requestedLabelSelector labels.Selector requestedLabelSelector labels.Selector
requestedFieldSelector fields.Selector requestedFieldSelector fields.Selector
requestedUninitialized bool
requestedResourceVersion string requestedResourceVersion string
requestedResourceNamespace string requestedResourceNamespace string
@ -390,7 +389,6 @@ func (storage *SimpleRESTStorage) List(ctx context.Context, options *metainterna
if options != nil && options.FieldSelector != nil { if options != nil && options.FieldSelector != nil {
storage.requestedFieldSelector = options.FieldSelector storage.requestedFieldSelector = options.FieldSelector
} }
storage.requestedUninitialized = options.IncludeUninitialized
return result, storage.errors["list"] return result, storage.errors["list"]
} }
@ -1689,52 +1687,6 @@ func TestGetCompression(t *testing.T) {
} }
} }
func TestGetUninitialized(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
list: []genericapitesting.Simple{
{
ObjectMeta: metav1.ObjectMeta{
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: "test"}},
},
},
Other: "foo",
},
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple"),
name: "id",
namespace: "default",
}
storage["simple"] = &simpleStorage
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple?includeUninitialized=true")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected response: %#v", resp)
}
var itemOut genericapitesting.SimpleList
body, err := extractBody(resp, &itemOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(itemOut.Items) != 1 || itemOut.Items[0].Other != "foo" {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
if !simpleStorage.requestedUninitialized {
t.Errorf("Didn't set correct flag")
}
}
func TestGetPretty(t *testing.T) { func TestGetPretty(t *testing.T) {
storage := map[string]rest.Storage{} storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{ simpleStorage := SimpleRESTStorage{

View File

@ -23,7 +23,6 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
@ -150,13 +149,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
} }
trace.Step("Object stored in database") trace.Step("Object stored in database")
// If the object is partially initialized, always indicate it via StatusAccepted
code := http.StatusCreated code := http.StatusCreated
if accessor, err := meta.Accessor(result); err == nil {
if accessor.GetInitializers() != nil {
code = http.StatusAccepted
}
}
status, ok := result.(*metav1.Status) status, ok := result.(*metav1.Status)
if ok && err == nil && status.Code == 0 { if ok && err == nil && status.Code == 0 {
status.Code = int32(code) status.Code = int32(code)

View File

@ -65,13 +65,6 @@ const (
// Enables compression of REST responses (GET and LIST only) // Enables compression of REST responses (GET and LIST only)
APIResponseCompression utilfeature.Feature = "APIResponseCompression" APIResponseCompression utilfeature.Feature = "APIResponseCompression"
// owner: @smarterclayton
// alpha: v1.7
//
// Allow asynchronous coordination of object creation.
// Auto-enabled by the Initializers admission plugin.
Initializers utilfeature.Feature = "Initializers"
// owner: @smarterclayton // owner: @smarterclayton
// alpha: v1.8 // alpha: v1.8
// beta: v1.9 // beta: v1.9
@ -103,7 +96,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA}, AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA},
DynamicAuditing: {Default: false, PreRelease: utilfeature.Alpha}, DynamicAuditing: {Default: false, PreRelease: utilfeature.Alpha},
APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha}, APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha},
Initializers: {Default: false, PreRelease: utilfeature.Alpha},
APIListChunking: {Default: true, PreRelease: utilfeature.Beta}, APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
DryRun: {Default: true, PreRelease: utilfeature.Beta}, DryRun: {Default: true, PreRelease: utilfeature.Beta},
} }

View File

@ -307,7 +307,6 @@ func (e *Store) ListPredicate(ctx context.Context, p storage.SelectionPredicate,
// By default we should serve the request from etcd. // By default we should serve the request from etcd.
options = &metainternalversion.ListOptions{ResourceVersion: ""} options = &metainternalversion.ListOptions{ResourceVersion: ""}
} }
p.IncludeUninitialized = options.IncludeUninitialized
p.Limit = options.Limit p.Limit = options.Limit
p.Continue = options.Continue p.Continue = options.Continue
list := e.NewListFunc() list := e.NewListFunc()
@ -380,92 +379,9 @@ func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation
return nil, err return nil, err
} }
} }
if !options.IncludeUninitialized {
return e.WaitForInitialized(ctx, out)
}
return out, nil return out, nil
} }
// WaitForInitialized holds until the object is initialized, or returns an error if the default limit expires.
// This method is exposed publicly for consumers of generic rest tooling.
func (e *Store) WaitForInitialized(ctx context.Context, obj runtime.Object) (runtime.Object, error) {
// return early if we don't have initializers, or if they've completed already
accessor, err := meta.Accessor(obj)
if err != nil {
return obj, nil
}
initializers := accessor.GetInitializers()
if initializers == nil {
return obj, nil
}
if result := initializers.Result; result != nil {
return nil, kubeerr.FromObject(result)
}
key, err := e.KeyFunc(ctx, accessor.GetName())
if err != nil {
return nil, err
}
qualifiedResource := e.qualifiedResourceFromContext(ctx)
w, err := e.Storage.Watch(ctx, key, accessor.GetResourceVersion(), storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
IncludeUninitialized: true,
})
if err != nil {
return nil, err
}
defer w.Stop()
latest := obj
ch := w.ResultChan()
for {
select {
case event, ok := <-ch:
if !ok {
msg := fmt.Sprintf("server has timed out waiting for the initialization of %s %s",
qualifiedResource.String(), accessor.GetName())
return nil, kubeerr.NewTimeoutError(msg, 0)
}
switch event.Type {
case watch.Deleted:
if latest = event.Object; latest != nil {
if accessor, err := meta.Accessor(latest); err == nil {
if initializers := accessor.GetInitializers(); initializers != nil && initializers.Result != nil {
// initialization failed, but we missed the modification event
return nil, kubeerr.FromObject(initializers.Result)
}
}
}
return nil, kubeerr.NewInternalError(fmt.Errorf("object deleted while waiting for creation"))
case watch.Error:
if status, ok := event.Object.(*metav1.Status); ok {
return nil, &kubeerr.StatusError{ErrStatus: *status}
}
return nil, kubeerr.NewInternalError(fmt.Errorf("unexpected object in watch stream, can't complete initialization %T", event.Object))
case watch.Modified:
latest = event.Object
accessor, err = meta.Accessor(latest)
if err != nil {
return nil, kubeerr.NewInternalError(fmt.Errorf("object no longer has access to metadata %T: %v", latest, err))
}
initializers := accessor.GetInitializers()
if initializers == nil {
// completed initialization
return latest, nil
}
if result := initializers.Result; result != nil {
// initialization failed
return nil, kubeerr.FromObject(result)
}
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
// shouldDeleteDuringUpdate checks if a Update is removing all the object's // shouldDeleteDuringUpdate checks if a Update is removing all the object's
// finalizers. If so, it further checks if the object's // finalizers. If so, it further checks if the object's
// DeletionGracePeriodSeconds is 0. // DeletionGracePeriodSeconds is 0.
@ -483,20 +399,6 @@ func (e *Store) shouldDeleteDuringUpdate(ctx context.Context, key string, obj, e
return len(newMeta.GetFinalizers()) == 0 && oldMeta.GetDeletionGracePeriodSeconds() != nil && *oldMeta.GetDeletionGracePeriodSeconds() == 0 return len(newMeta.GetFinalizers()) == 0 && oldMeta.GetDeletionGracePeriodSeconds() != nil && *oldMeta.GetDeletionGracePeriodSeconds() == 0
} }
// shouldDeleteForFailedInitialization returns true if the provided object is initializing and has
// a failure recorded.
func (e *Store) shouldDeleteForFailedInitialization(ctx context.Context, obj runtime.Object) bool {
m, err := meta.Accessor(obj)
if err != nil {
utilruntime.HandleError(err)
return false
}
if initializers := m.GetInitializers(); initializers != nil && initializers.Result != nil {
return true
}
return false
}
// deleteWithoutFinalizers handles deleting an object ignoring its finalizer list. // deleteWithoutFinalizers handles deleting an object ignoring its finalizer list.
// Used for objects that are either been finalized or have never initialized. // Used for objects that are either been finalized or have never initialized.
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions, dryRun bool) (runtime.Object, bool, error) { func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions, dryRun bool) (runtime.Object, bool, error) {
@ -652,10 +554,6 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
return nil, false, err return nil, false, err
} }
if e.shouldDeleteForFailedInitialization(ctx, out) {
return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions, dryrun.IsDryRun(options.DryRun))
}
if creating { if creating {
if e.AfterCreate != nil { if e.AfterCreate != nil {
if err := e.AfterCreate(out); err != nil { if err := e.AfterCreate(out); err != nil {
@ -1055,11 +953,6 @@ func (e *Store) DeleteCollection(ctx context.Context, options *metav1.DeleteOpti
listOptions = listOptions.DeepCopy() listOptions = listOptions.DeepCopy()
} }
// DeleteCollection must remain backwards compatible with old clients that expect it to
// remove all resources, initialized or not, within the type. It is also consistent with
// Delete which does not require IncludeUninitialized
listOptions.IncludeUninitialized = true
listObj, err := e.List(ctx, listOptions) listObj, err := e.List(ctx, listOptions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1174,7 +1067,6 @@ func (e *Store) Watch(ctx context.Context, options *metainternalversion.ListOpti
resourceVersion := "" resourceVersion := ""
if options != nil { if options != nil {
resourceVersion = options.ResourceVersion resourceVersion = options.ResourceVersion
predicate.IncludeUninitialized = options.IncludeUninitialized
} }
return e.WatchPredicate(ctx, predicate, resourceVersion) return e.WatchPredicate(ctx, predicate, resourceVersion)
} }

View File

@ -42,11 +42,9 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/apis/example" "k8s.io/apiserver/pkg/apis/example"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1" examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
@ -56,8 +54,6 @@ import (
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
"k8s.io/apiserver/pkg/storage/storagebackend/factory" "k8s.io/apiserver/pkg/storage/storagebackend/factory"
storagetesting "k8s.io/apiserver/pkg/storage/testing" storagetesting "k8s.io/apiserver/pkg/storage/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
) )
var scheme = runtime.NewScheme() var scheme = runtime.NewScheme()
@ -127,9 +123,9 @@ func NewTestGenericStoreRegistry(t *testing.T) (factory.DestroyFunc, *Store) {
return newTestGenericStoreRegistry(t, scheme, false) return newTestGenericStoreRegistry(t, scheme, false)
} }
func getPodAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func getPodAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return labels.Set{"name": pod.ObjectMeta.Name}, nil, pod.Initializers != nil, nil return labels.Set{"name": pod.ObjectMeta.Name}, nil, nil
} }
// matchPodName returns selection predicate that matches any pod with name in the set. // matchPodName returns selection predicate that matches any pod with name in the set.
@ -152,8 +148,8 @@ func matchEverything() storage.SelectionPredicate {
return storage.SelectionPredicate{ return storage.SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.Everything(), Field: fields.Everything(),
GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, err error) {
return nil, nil, false, nil return nil, nil, nil
}, },
} }
} }
@ -379,33 +375,6 @@ func TestStoreCreate(t *testing.T) {
} }
} }
func isPendingInitialization(obj metav1.Object) bool {
return obj.GetInitializers() != nil && obj.GetInitializers().Result == nil && len(obj.GetInitializers().Pending) > 0
}
func hasInitializers(obj metav1.Object, expected ...string) bool {
if !isPendingInitialization(obj) {
return false
}
if len(expected) != len(obj.GetInitializers().Pending) {
return false
}
for i, init := range obj.GetInitializers().Pending {
if init.Name != expected[i] {
return false
}
}
return true
}
func isFailedInitialization(obj metav1.Object) bool {
return obj.GetInitializers() != nil && obj.GetInitializers().Result != nil && obj.GetInitializers().Result.Status == metav1.StatusFailure
}
func isInitialized(obj metav1.Object) bool {
return obj.GetInitializers() == nil
}
func isQualifiedResource(err error, kind, group string) bool { func isQualifiedResource(err error, kind, group string) bool {
if err.(errors.APIStatus).Status().Details.Kind != kind || err.(errors.APIStatus).Status().Details.Group != group { if err.(errors.APIStatus).Status().Details.Kind != kind || err.(errors.APIStatus).Status().Details.Group != group {
return false return false
@ -413,185 +382,6 @@ func isQualifiedResource(err error, kind, group string) bool {
return true return true
} }
func TestStoreCreateInitialized(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo", Namespace: "test",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
Spec: example.PodSpec{NodeName: "machine"},
}
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
ch := make(chan struct{})
chObserver := make(chan struct{})
// simulate a background initializer that initializes the object
early := make(chan struct{}, 1)
go func() {
defer close(ch)
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: true,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
defer w.Stop()
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !hasInitializers(pod, validInitializerName) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
select {
case <-early:
t.Fatalf("CreateInitialized should not have returned")
default:
}
pod.Initializers = nil
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatal(err)
}
pod = updated.(*example.Pod)
if !isInitialized(pod) {
t.Fatalf("unexpected update: %#v", pod.Initializers)
}
event = <-w.ResultChan()
if event.Type != watch.Modified || !isInitialized(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
}()
// create a background worker that should only observe the final creation
go func() {
defer close(chObserver)
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: false,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
defer w.Stop()
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !isInitialized(pod) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
}()
// create the object
objA, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// signal that we're now waiting, then wait for both observers to see
// the result of the create.
early <- struct{}{}
<-ch
<-chObserver
// get the object
checkobj, err := registry.Get(ctx, podA.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// verify objects are equal
if e, a := objA, checkobj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
func TestStoreCreateInitializedFailed(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo", Namespace: "test",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
Spec: example.PodSpec{NodeName: "machine"},
}
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
ch := make(chan struct{})
go func() {
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: true,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !hasInitializers(pod, validInitializerName) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
pod.Initializers.Pending = nil
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure, Code: 403, Reason: metav1.StatusReasonForbidden, Message: "induced failure"}
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatal(err)
}
pod = updated.(*example.Pod)
if !isFailedInitialization(pod) {
t.Fatalf("unexpected update: %#v", pod.Initializers)
}
event = <-w.ResultChan()
if event.Type != watch.Modified || !isFailedInitialization(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
event = <-w.ResultChan()
if event.Type != watch.Deleted || !isFailedInitialization(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
w.Stop()
close(ch)
}()
// create the object
_, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if !errors.IsForbidden(err) {
t.Fatalf("unexpected error: %#v", err.(errors.APIStatus).Status())
}
if err.(errors.APIStatus).Status().Message != "induced failure" {
t.Fatalf("unexpected error: %#v", err)
}
<-ch
// get the object
_, err = registry.Get(ctx, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
}
func updateAndVerify(t *testing.T, ctx context.Context, registry *Store, pod *example.Pod) bool { func updateAndVerify(t *testing.T, ctx context.Context, registry *Store, pod *example.Pod) bool {
obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil { if err != nil {
@ -897,44 +687,6 @@ func TestStoreDelete(t *testing.T) {
} }
} }
func TestStoreDeleteUninitialized(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: validInitializerName}}}},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// test failure condition
_, _, err := registry.Delete(testContext, podA.Name, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
// create pod
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// delete object
_, wasDeleted, err := registry.Delete(testContext, podA.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, pod %s should have been deleted immediately", podA.Name)
}
// try to get a item which should be deleted
_, err = registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
// TestGracefulStoreCanDeleteIfExistingGracePeriodZero tests recovery from // TestGracefulStoreCanDeleteIfExistingGracePeriodZero tests recovery from
// race condition where the graceful delete is unable to complete // race condition where the graceful delete is unable to complete
// in prior operation, but the pod remains with deletion timestamp // in prior operation, but the pod remains with deletion timestamp
@ -1040,45 +792,6 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
} }
} }
func TestFailedInitializationStoreUpdate(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
initialGeneration := int64(1)
podInitializing := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: validInitializerName}}}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
registry.EnableGarbageCollection = true
defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
defer destroyFunc()
// create pod, view initializing
obj, err := registry.Create(testContext, podInitializing, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
pod := obj.(*example.Pod)
// update the pod with initialization failure, the pod should be deleted
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure}
result, _, err := registry.Update(testContext, podInitializing.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = registry.Get(testContext, podInitializing.Name, &metav1.GetOptions{})
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
pod = result.(*example.Pod)
if pod.Initializers == nil || pod.Initializers.Result == nil || pod.Initializers.Result.Status != metav1.StatusFailure {
t.Fatalf("Pod returned from update was not correct: %#v", pod)
}
}
func TestNonGracefulStoreHandleFinalizers(t *testing.T) { func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
initialGeneration := int64(1) initialGeneration := int64(1)
podWithFinalizer := &example.Pod{ podWithFinalizer := &example.Pod{
@ -1656,14 +1369,6 @@ func TestStoreDeletionPropagation(t *testing.T) {
func TestStoreDeleteCollection(t *testing.T) { func TestStoreDeleteCollection(t *testing.T) {
podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
podB := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} podB := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
podC := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t) destroyFunc, registry := NewTestGenericStoreRegistry(t)
@ -1675,9 +1380,6 @@ func TestStoreDeleteCollection(t *testing.T) {
if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
if _, err := registry.Create(testContext, podC, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Delete all pods. // Delete all pods.
deleted, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}) deleted, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{})
@ -1685,7 +1387,7 @@ func TestStoreDeleteCollection(t *testing.T) {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
deletedPods := deleted.(*example.PodList) deletedPods := deleted.(*example.PodList)
if len(deletedPods.Items) != 3 { if len(deletedPods.Items) != 2 {
t.Errorf("Unexpected number of pods deleted: %d, expected: 3", len(deletedPods.Items)) t.Errorf("Unexpected number of pods deleted: %d, expected: 3", len(deletedPods.Items))
} }
@ -1695,9 +1397,6 @@ func TestStoreDeleteCollection(t *testing.T) {
if _, err := registry.Get(testContext, podB.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) { if _, err := registry.Get(testContext, podB.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
if _, err := registry.Get(testContext, podC.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
} }
func TestStoreDeleteCollectionNotFound(t *testing.T) { func TestStoreDeleteCollectionNotFound(t *testing.T) {
@ -1892,12 +1591,12 @@ func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheE
return storage.SelectionPredicate{ return storage.SelectionPredicate{
Label: label, Label: label,
Field: field, Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod, ok := obj.(*example.Pod) pod, ok := obj.(*example.Pod)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a pod") return nil, nil, fmt.Errorf("not a pod")
} }
return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true), pod.Initializers != nil, nil return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true), nil
}, },
} }
}, },

View File

@ -28,9 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
) )
// RESTCreateStrategy defines the minimum validation, accepted input, and // RESTCreateStrategy defines the minimum validation, accepted input, and
@ -92,11 +90,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.
objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName())) objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName()))
} }
// Ensure Initializers are not set unless the feature is enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
objectMeta.SetInitializers(nil) objectMeta.SetInitializers(nil)
}
// ClusterName is ignored and should not be saved // ClusterName is ignored and should not be saved
if len(objectMeta.GetClusterName()) > 0 { if len(objectMeta.GetClusterName()) > 0 {
objectMeta.SetClusterName("") objectMeta.SetClusterName("")

View File

@ -176,8 +176,7 @@ type Creater interface {
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object) // This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object New() runtime.Object
// Create creates a new version of a resource. If includeUninitialized is set, the object may be returned // Create creates a new version of a resource.
// without completing initialization.
Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
} }
@ -189,8 +188,7 @@ type NamedCreater interface {
// Create creates a new version of a resource. It expects a name parameter from the path. // Create creates a new version of a resource. It expects a name parameter from the path.
// This is needed for create operations on subresources which include the name of the parent // This is needed for create operations on subresources which include the name of the parent
// resource in the path. If includeUninitialized is set, the object may be returned without // resource in the path.
// completing initialization.
Create(ctx context.Context, name string, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) Create(ctx context.Context, name string, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
} }

View File

@ -28,8 +28,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
) )
// RESTUpdateStrategy defines the minimum validation, accepted input, and // RESTUpdateStrategy defines the minimum validation, accepted input, and
@ -104,12 +102,8 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old run
} }
objectMeta.SetGeneration(oldMeta.GetGeneration()) objectMeta.SetGeneration(oldMeta.GetGeneration())
// Ensure Initializers are not set unless the feature is enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
oldMeta.SetInitializers(nil) oldMeta.SetInitializers(nil)
objectMeta.SetInitializers(nil) objectMeta.SetInitializers(nil)
}
strategy.PrepareForUpdate(ctx, obj, old) strategy.PrepareForUpdate(ctx, obj, old)
// ClusterName is ignored and should not be saved // ClusterName is ignored and should not be saved

View File

@ -62,8 +62,8 @@ type Config struct {
// KeyFunc is used to get a key in the underlying storage for a given object. // KeyFunc is used to get a key in the underlying storage for a given object.
KeyFunc func(runtime.Object) (string, error) KeyFunc func(runtime.Object) (string, error)
// GetAttrsFunc is used to get object labels, fields, and the uninitialized bool // GetAttrsFunc is used to get object labels, fields
GetAttrsFunc func(runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) GetAttrsFunc func(runtime.Object) (label labels.Set, field fields.Set, err error)
// TriggerPublisherFunc is used for optimizing amount of watchers that // TriggerPublisherFunc is used for optimizing amount of watchers that
// needs to process an incoming event. // needs to process an incoming event.
@ -131,7 +131,7 @@ func (i *indexedWatchers) terminateAll(objectType reflect.Type) {
} }
} }
type filterWithAttrsFunc func(key string, l labels.Set, f fields.Set, uninitialized bool) bool type filterWithAttrsFunc func(key string, l labels.Set, f fields.Set) bool
// Cacher is responsible for serving WATCH and LIST requests for a given // Cacher is responsible for serving WATCH and LIST requests for a given
// resource from its internal cache and updating its cache in the background // resource from its internal cache and updating its cache in the background
@ -458,7 +458,7 @@ func (c *Cacher) GetToList(ctx context.Context, key string, resourceVersion stri
if !ok { if !ok {
return fmt.Errorf("non *storeElement returned from storage: %v", obj) return fmt.Errorf("non *storeElement returned from storage: %v", obj)
} }
if filter(elem.Key, elem.Labels, elem.Fields, elem.Uninitialized) { if filter(elem.Key, elem.Labels, elem.Fields) {
listVal.Set(reflect.Append(listVal, reflect.ValueOf(elem.Object).Elem())) listVal.Set(reflect.Append(listVal, reflect.ValueOf(elem.Object).Elem()))
} }
} }
@ -532,7 +532,7 @@ func (c *Cacher) List(ctx context.Context, key string, resourceVersion string, p
if !ok { if !ok {
return fmt.Errorf("non *storeElement returned from storage: %v", obj) return fmt.Errorf("non *storeElement returned from storage: %v", obj)
} }
if filter(elem.Key, elem.Labels, elem.Fields, elem.Uninitialized) { if filter(elem.Key, elem.Labels, elem.Fields) {
listVal.Set(reflect.Append(listVal, reflect.ValueOf(elem.Object).Elem())) listVal.Set(reflect.Append(listVal, reflect.ValueOf(elem.Object).Elem()))
} }
} }
@ -693,11 +693,11 @@ func forgetWatcher(c *Cacher, index int, triggerValue string, triggerSupported b
} }
func filterWithAttrsFunction(key string, p storage.SelectionPredicate) filterWithAttrsFunc { func filterWithAttrsFunction(key string, p storage.SelectionPredicate) filterWithAttrsFunc {
filterFunc := func(objKey string, label labels.Set, field fields.Set, uninitialized bool) bool { filterFunc := func(objKey string, label labels.Set, field fields.Set) bool {
if !hasPathPrefix(objKey, key) { if !hasPathPrefix(objKey, key) {
return false return false
} }
return p.MatchesObjectAttributes(label, field, uninitialized) return p.MatchesObjectAttributes(label, field)
} }
return filterFunc return filterFunc
} }
@ -871,10 +871,10 @@ func (c *cacheWatcher) add(event *watchCacheEvent, budget *timeBudget) {
// NOTE: sendWatchCacheEvent is assumed to not modify <event> !!! // NOTE: sendWatchCacheEvent is assumed to not modify <event> !!!
func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) { func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields, event.ObjUninitialized) curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields)
oldObjPasses := false oldObjPasses := false
if event.PrevObject != nil { if event.PrevObject != nil {
oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields, event.PrevObjUninitialized) oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields)
} }
if !curObjPasses && !oldObjPasses { if !curObjPasses && !oldObjPasses {
// Watcher is not interested in that object. // Watcher is not interested in that object.

View File

@ -46,7 +46,7 @@ import (
func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) { func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) {
var lock sync.RWMutex var lock sync.RWMutex
count := 0 count := 0
filter := func(string, labels.Set, fields.Set, bool) bool { return true } filter := func(string, labels.Set, fields.Set) bool { return true }
forget := func(bool) { forget := func(bool) {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
@ -70,7 +70,7 @@ func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) {
} }
func TestCacheWatcherHandlesFiltering(t *testing.T) { func TestCacheWatcherHandlesFiltering(t *testing.T) {
filter := func(_ string, _ labels.Set, field fields.Set, _ bool) bool { filter := func(_ string, _ labels.Set, field fields.Set) bool {
return field["spec.nodeName"] == "host" return field["spec.nodeName"] == "host"
} }
forget := func(bool) {} forget := func(bool) {}
@ -240,7 +240,7 @@ func newTestCacher(s storage.Interface, cap int) (*Cacher, storage.Versioner) {
Type: &example.Pod{}, Type: &example.Pod{},
ResourcePrefix: prefix, ResourcePrefix: prefix,
KeyFunc: func(obj runtime.Object) (string, error) { return storage.NamespaceKeyFunc(prefix, obj) }, KeyFunc: func(obj runtime.Object) (string, error) { return storage.NamespaceKeyFunc(prefix, obj) },
GetAttrsFunc: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { return nil, nil, true, nil }, GetAttrsFunc: func(obj runtime.Object) (labels.Set, fields.Set, error) { return nil, nil, nil },
NewListFunc: func() runtime.Object { return &example.PodList{} }, NewListFunc: func() runtime.Object { return &example.PodList{} },
Codec: codecs.LegacyCodec(examplev1.SchemeGroupVersion), Codec: codecs.LegacyCodec(examplev1.SchemeGroupVersion),
} }

View File

@ -46,30 +46,27 @@ const (
// the previous value of the object to enable proper filtering in the // the previous value of the object to enable proper filtering in the
// upper layers. // upper layers.
type watchCacheEvent struct { type watchCacheEvent struct {
Type watch.EventType Type watch.EventType
Object runtime.Object Object runtime.Object
ObjLabels labels.Set ObjLabels labels.Set
ObjFields fields.Set ObjFields fields.Set
ObjUninitialized bool PrevObject runtime.Object
PrevObject runtime.Object PrevObjLabels labels.Set
PrevObjLabels labels.Set PrevObjFields fields.Set
PrevObjFields fields.Set Key string
PrevObjUninitialized bool ResourceVersion uint64
Key string
ResourceVersion uint64
} }
// Computing a key of an object is generally non-trivial (it performs // Computing a key of an object is generally non-trivial (it performs
// e.g. validation underneath). Similarly computing object fields and // e.g. validation underneath). Similarly computing object fields and
// labels. To avoid computing them multiple times (to serve the event // labels. To avoid computing them multiple times (to serve the event
// in different List/Watch requests), in the underlying store we are // in different List/Watch requests), in the underlying store we are
// keeping structs (key, object, labels, fields, uninitialized). // keeping structs (key, object, labels, fields).
type storeElement struct { type storeElement struct {
Key string Key string
Object runtime.Object Object runtime.Object
Labels labels.Set Labels labels.Set
Fields fields.Set Fields fields.Set
Uninitialized bool
} }
func storeElementKey(obj interface{}) (string, error) { func storeElementKey(obj interface{}) (string, error) {
@ -107,7 +104,7 @@ type watchCache struct {
keyFunc func(runtime.Object) (string, error) keyFunc func(runtime.Object) (string, error)
// getAttrsFunc is used to get labels and fields of an object. // getAttrsFunc is used to get labels and fields of an object.
getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, bool, error) getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error)
// cache is used a cyclic buffer - its first element (with the smallest // cache is used a cyclic buffer - its first element (with the smallest
// resourceVersion) is defined by startIndex, its last element is defined // resourceVersion) is defined by startIndex, its last element is defined
@ -147,7 +144,7 @@ type watchCache struct {
func newWatchCache( func newWatchCache(
capacity int, capacity int,
keyFunc func(runtime.Object) (string, error), keyFunc func(runtime.Object) (string, error),
getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, bool, error), getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error),
versioner storage.Versioner) *watchCache { versioner storage.Versioner) *watchCache {
wc := &watchCache{ wc := &watchCache{
capacity: capacity, capacity: capacity,
@ -220,19 +217,18 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
return fmt.Errorf("couldn't compute key: %v", err) return fmt.Errorf("couldn't compute key: %v", err)
} }
elem := &storeElement{Key: key, Object: event.Object} elem := &storeElement{Key: key, Object: event.Object}
elem.Labels, elem.Fields, elem.Uninitialized, err = w.getAttrsFunc(event.Object) elem.Labels, elem.Fields, err = w.getAttrsFunc(event.Object)
if err != nil { if err != nil {
return err return err
} }
watchCacheEvent := &watchCacheEvent{ watchCacheEvent := &watchCacheEvent{
Type: event.Type, Type: event.Type,
Object: elem.Object, Object: elem.Object,
ObjLabels: elem.Labels, ObjLabels: elem.Labels,
ObjFields: elem.Fields, ObjFields: elem.Fields,
ObjUninitialized: elem.Uninitialized, Key: key,
Key: key, ResourceVersion: resourceVersion,
ResourceVersion: resourceVersion,
} }
// TODO: We should consider moving this lock below after the watchCacheEvent // TODO: We should consider moving this lock below after the watchCacheEvent
@ -250,7 +246,6 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
watchCacheEvent.PrevObject = previousElem.Object watchCacheEvent.PrevObject = previousElem.Object
watchCacheEvent.PrevObjLabels = previousElem.Labels watchCacheEvent.PrevObjLabels = previousElem.Labels
watchCacheEvent.PrevObjFields = previousElem.Fields watchCacheEvent.PrevObjFields = previousElem.Fields
watchCacheEvent.PrevObjUninitialized = previousElem.Uninitialized
} }
if w.onEvent != nil { if w.onEvent != nil {
@ -373,16 +368,15 @@ func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error {
if err != nil { if err != nil {
return fmt.Errorf("couldn't compute key: %v", err) return fmt.Errorf("couldn't compute key: %v", err)
} }
objLabels, objFields, objUninitialized, err := w.getAttrsFunc(object) objLabels, objFields, err := w.getAttrsFunc(object)
if err != nil { if err != nil {
return err return err
} }
toReplace = append(toReplace, &storeElement{ toReplace = append(toReplace, &storeElement{
Key: key, Key: key,
Object: object, Object: object,
Labels: objLabels, Labels: objLabels,
Fields: objFields, Fields: objFields,
Uninitialized: objUninitialized,
}) })
} }
@ -451,18 +445,17 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*w
if !ok { if !ok {
return nil, fmt.Errorf("not a storeElement: %v", elem) return nil, fmt.Errorf("not a storeElement: %v", elem)
} }
objLabels, objFields, objUninitialized, err := w.getAttrsFunc(elem.Object) objLabels, objFields, err := w.getAttrsFunc(elem.Object)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result[i] = &watchCacheEvent{ result[i] = &watchCacheEvent{
Type: watch.Added, Type: watch.Added,
Object: elem.Object, Object: elem.Object,
ObjLabels: objLabels, ObjLabels: objLabels,
ObjFields: objFields, ObjFields: objFields,
ObjUninitialized: objUninitialized, Key: elem.Key,
Key: elem.Key, ResourceVersion: w.resourceVersion,
ResourceVersion: w.resourceVersion,
} }
} }
return result, nil return result, nil

View File

@ -56,11 +56,10 @@ func makeTestPod(name string, resourceVersion uint64) *v1.Pod {
func makeTestStoreElement(pod *v1.Pod) *storeElement { func makeTestStoreElement(pod *v1.Pod) *storeElement {
return &storeElement{ return &storeElement{
Key: "prefix/ns/" + pod.Name, Key: "prefix/ns/" + pod.Name,
Object: pod, Object: pod,
Labels: labels.Set(pod.Labels), Labels: labels.Set(pod.Labels),
Fields: fields.Set{"spec.nodeName": pod.Spec.NodeName}, Fields: fields.Set{"spec.nodeName": pod.Spec.NodeName},
Uninitialized: false,
} }
} }
@ -69,12 +68,12 @@ func newTestWatchCache(capacity int) *watchCache {
keyFunc := func(obj runtime.Object) (string, error) { keyFunc := func(obj runtime.Object) (string, error) {
return storage.NamespaceKeyFunc("prefix", obj) return storage.NamespaceKeyFunc("prefix", obj)
} }
getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod, ok := obj.(*v1.Pod) pod, ok := obj.(*v1.Pod)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a pod") return nil, nil, fmt.Errorf("not a pod")
} }
return labels.Set(pod.Labels), fields.Set{"spec.nodeName": pod.Spec.NodeName}, false, nil return labels.Set(pod.Labels), fields.Set{"spec.nodeName": pod.Spec.NodeName}, nil
} }
versioner := etcd.APIObjectVersioner{} versioner := etcd.APIObjectVersioner{}
wc := newWatchCache(capacity, keyFunc, getAttrsFunc, versioner) wc := newWatchCache(capacity, keyFunc, getAttrsFunc, versioner)

View File

@ -314,9 +314,9 @@ func TestGetToList(t *testing.T) {
pred: storage.SelectionPredicate{ pred: storage.SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name), Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
}, },
expectedOut: nil, expectedOut: nil,
@ -819,9 +819,9 @@ func TestList(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
getAttrs := func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { getAttrs := func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
} }
tests := []struct { tests := []struct {
@ -1124,9 +1124,9 @@ func TestListContinuation(t *testing.T) {
Continue: continueValue, Continue: continueValue,
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.Everything(), Field: fields.Everything(),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
} }
} }
@ -1233,9 +1233,9 @@ func TestListInconsistentContinuation(t *testing.T) {
Continue: continueValue, Continue: continueValue,
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.Everything(), Field: fields.Everything(),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
} }
} }

View File

@ -72,9 +72,9 @@ func testWatch(t *testing.T, recursive bool) {
pred: storage.SelectionPredicate{ pred: storage.SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.ParseSelectorOrDie("metadata.name=bar"), Field: fields.ParseSelectorOrDie("metadata.name=bar"),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
}, },
}, { // update }, { // update
@ -87,9 +87,9 @@ func testWatch(t *testing.T, recursive bool) {
pred: storage.SelectionPredicate{ pred: storage.SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.ParseSelectorOrDie("metadata.name!=bar"), Field: fields.ParseSelectorOrDie("metadata.name!=bar"),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
}, },
}} }}

View File

@ -86,8 +86,6 @@ type TriggerPublisherFunc func(obj runtime.Object) []MatchValue
var Everything = SelectionPredicate{ var Everything = SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.Everything(), Field: fields.Everything(),
// TODO: split this into a new top level constant?
IncludeUninitialized: true,
} }
// Pass an UpdateFunc to Interface.GuaranteedUpdate to make an update // Pass an UpdateFunc to Interface.GuaranteedUpdate to make an update

View File

@ -25,59 +25,58 @@ import (
// AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match. // AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match.
// In any failure to parse given object, it returns error. // In any failure to parse given object, it returns error.
type AttrFunc func(obj runtime.Object) (labels.Set, fields.Set, bool, error) type AttrFunc func(obj runtime.Object) (labels.Set, fields.Set, error)
// FieldMutationFunc allows the mutation of the field selection fields. It is mutating to // FieldMutationFunc allows the mutation of the field selection fields. It is mutating to
// avoid the extra allocation on this common path // avoid the extra allocation on this common path
type FieldMutationFunc func(obj runtime.Object, fieldSet fields.Set) error type FieldMutationFunc func(obj runtime.Object, fieldSet fields.Set) error
func DefaultClusterScopedAttr(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func DefaultClusterScopedAttr(obj runtime.Object) (labels.Set, fields.Set, error) {
metadata, err := meta.Accessor(obj) metadata, err := meta.Accessor(obj)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
fieldSet := fields.Set{ fieldSet := fields.Set{
"metadata.name": metadata.GetName(), "metadata.name": metadata.GetName(),
} }
return labels.Set(metadata.GetLabels()), fieldSet, metadata.GetInitializers() != nil, nil return labels.Set(metadata.GetLabels()), fieldSet, nil
} }
func DefaultNamespaceScopedAttr(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func DefaultNamespaceScopedAttr(obj runtime.Object) (labels.Set, fields.Set, error) {
metadata, err := meta.Accessor(obj) metadata, err := meta.Accessor(obj)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
fieldSet := fields.Set{ fieldSet := fields.Set{
"metadata.name": metadata.GetName(), "metadata.name": metadata.GetName(),
"metadata.namespace": metadata.GetNamespace(), "metadata.namespace": metadata.GetNamespace(),
} }
return labels.Set(metadata.GetLabels()), fieldSet, metadata.GetInitializers() != nil, nil return labels.Set(metadata.GetLabels()), fieldSet, nil
} }
func (f AttrFunc) WithFieldMutation(fieldMutator FieldMutationFunc) AttrFunc { func (f AttrFunc) WithFieldMutation(fieldMutator FieldMutationFunc) AttrFunc {
return func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { return func(obj runtime.Object) (labels.Set, fields.Set, error) {
labelSet, fieldSet, initialized, err := f(obj) labelSet, fieldSet, err := f(obj)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
if err := fieldMutator(obj, fieldSet); err != nil { if err := fieldMutator(obj, fieldSet); err != nil {
return nil, nil, false, err return nil, nil, err
} }
return labelSet, fieldSet, initialized, nil return labelSet, fieldSet, nil
} }
} }
// SelectionPredicate is used to represent the way to select objects from api storage. // SelectionPredicate is used to represent the way to select objects from api storage.
type SelectionPredicate struct { type SelectionPredicate struct {
Label labels.Selector Label labels.Selector
Field fields.Selector Field fields.Selector
IncludeUninitialized bool GetAttrs AttrFunc
GetAttrs AttrFunc IndexFields []string
IndexFields []string Limit int64
Limit int64 Continue string
Continue string
} }
// Matches returns true if the given object's labels and fields (as // Matches returns true if the given object's labels and fields (as
@ -87,13 +86,10 @@ func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) {
if s.Empty() { if s.Empty() {
return true, nil return true, nil
} }
labels, fields, uninitialized, err := s.GetAttrs(obj) labels, fields, err := s.GetAttrs(obj)
if err != nil { if err != nil {
return false, err return false, err
} }
if !s.IncludeUninitialized && uninitialized {
return false, nil
}
matched := s.Label.Matches(labels) matched := s.Label.Matches(labels)
if matched && s.Field != nil { if matched && s.Field != nil {
matched = matched && s.Field.Matches(fields) matched = matched && s.Field.Matches(fields)
@ -103,10 +99,7 @@ func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) {
// MatchesObjectAttributes returns true if the given labels and fields // MatchesObjectAttributes returns true if the given labels and fields
// match s.Label and s.Field. // match s.Label and s.Field.
func (s *SelectionPredicate) MatchesObjectAttributes(l labels.Set, f fields.Set, uninitialized bool) bool { func (s *SelectionPredicate) MatchesObjectAttributes(l labels.Set, f fields.Set) bool {
if !s.IncludeUninitialized && uninitialized {
return false
}
if s.Label.Empty() && s.Field.Empty() { if s.Label.Empty() && s.Field.Empty() {
return true return true
} }
@ -146,5 +139,5 @@ func (s *SelectionPredicate) MatcherIndex() []MatchValue {
// Empty returns true if the predicate performs no filtering. // Empty returns true if the predicate performs no filtering.
func (s *SelectionPredicate) Empty() bool { func (s *SelectionPredicate) Empty() bool {
return s.Label.Empty() && s.Field.Empty() && s.IncludeUninitialized return s.Label.Empty() && s.Field.Empty()
} }

View File

@ -48,7 +48,6 @@ func TestSelectionPredicate(t *testing.T) {
labelSelector, fieldSelector string labelSelector, fieldSelector string
labels labels.Set labels labels.Set
fields fields.Set fields fields.Set
uninitialized bool
err error err error
shouldMatch bool shouldMatch bool
matchSingleKey string matchSingleKey string
@ -81,14 +80,6 @@ func TestSelectionPredicate(t *testing.T) {
shouldMatch: true, shouldMatch: true,
matchSingleKey: "12345", matchSingleKey: "12345",
}, },
"E": {
fieldSelector: "metadata.name=12345",
labels: labels.Set{},
fields: fields.Set{"metadata.name": "12345"},
uninitialized: true,
shouldMatch: false,
matchSingleKey: "12345",
},
"error": { "error": {
labelSelector: "name=foo", labelSelector: "name=foo",
fieldSelector: "uid=12345", fieldSelector: "uid=12345",
@ -109,8 +100,8 @@ func TestSelectionPredicate(t *testing.T) {
sp := &SelectionPredicate{ sp := &SelectionPredicate{
Label: parsedLabel, Label: parsedLabel,
Field: parsedField, Field: parsedField,
GetAttrs: func(runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { GetAttrs: func(runtime.Object) (label labels.Set, field fields.Set, err error) {
return item.labels, item.fields, item.uninitialized, item.err return item.labels, item.fields, item.err
}, },
} }
got, err := sp.Matches(&Ignored{}) got, err := sp.Matches(&Ignored{})

View File

@ -61,12 +61,12 @@ func init() {
} }
// GetAttrs returns labels and fields of a given object for filtering purposes. // GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
pod, ok := obj.(*example.Pod) pod, ok := obj.(*example.Pod)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("not a pod") return nil, nil, fmt.Errorf("not a pod")
} }
return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), pod.Initializers != nil, nil return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil
} }
// PodToSelectableFields returns a field set that represents the object // PodToSelectableFields returns a field set that represents the object
@ -194,9 +194,9 @@ func TestGetToList(t *testing.T) {
pred: storage.SelectionPredicate{ pred: storage.SelectionPredicate{
Label: labels.Everything(), Label: labels.Everything(),
Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name), Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name),
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
pod := obj.(*example.Pod) pod := obj.(*example.Pod)
return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil return nil, fields.Set{"metadata.name": pod.Name}, nil
}, },
}, },
expectedOut: nil, expectedOut: nil,
@ -520,12 +520,12 @@ func TestFiltering(t *testing.T) {
pred := storage.SelectionPredicate{ pred := storage.SelectionPredicate{
Label: labels.SelectorFromSet(labels.Set{"filter": "foo"}), Label: labels.SelectorFromSet(labels.Set{"filter": "foo"}),
Field: fields.Everything(), Field: fields.Everything(),
GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, err error) {
metadata, err := meta.Accessor(obj) metadata, err := meta.Accessor(obj)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
return labels.Set(metadata.GetLabels()), nil, metadata.GetInitializers() != nil, nil return labels.Set(metadata.GetLabels()), nil, nil
}, },
} }
watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, pred) watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, pred)

View File

@ -28,12 +28,11 @@ import (
type ResourceBuilderFlags struct { type ResourceBuilderFlags struct {
FileNameFlags *FileNameFlags FileNameFlags *FileNameFlags
LabelSelector *string LabelSelector *string
FieldSelector *string FieldSelector *string
AllNamespaces *bool AllNamespaces *bool
All *bool All *bool
Local *bool Local *bool
IncludeUninitialized *bool
Scheme *runtime.Scheme Scheme *runtime.Scheme
Latest bool Latest bool
@ -88,12 +87,6 @@ func (o *ResourceBuilderFlags) WithLocal(defaultVal bool) *ResourceBuilderFlags
return o return o
} }
// WithUninitialized is using an alpha feature and may be dropped
func (o *ResourceBuilderFlags) WithUninitialized(defaultVal bool) *ResourceBuilderFlags {
o.IncludeUninitialized = &defaultVal
return o
}
func (o *ResourceBuilderFlags) WithScheme(scheme *runtime.Scheme) *ResourceBuilderFlags { func (o *ResourceBuilderFlags) WithScheme(scheme *runtime.Scheme) *ResourceBuilderFlags {
o.Scheme = scheme o.Scheme = scheme
return o return o
@ -128,9 +121,6 @@ func (o *ResourceBuilderFlags) AddFlags(flagset *pflag.FlagSet) {
if o.Local != nil { if o.Local != nil {
flagset.BoolVar(o.Local, "local", *o.Local, "If true, annotation will NOT contact api-server but run locally.") flagset.BoolVar(o.Local, "local", *o.Local, "If true, annotation will NOT contact api-server but run locally.")
} }
if o.IncludeUninitialized != nil {
flagset.BoolVar(o.IncludeUninitialized, "include-uninitialized", *o.IncludeUninitialized, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`)
}
} }
// ToBuilder gives you back a resource finder to visit resources that are located // ToBuilder gives you back a resource finder to visit resources that are located
@ -179,10 +169,6 @@ func (o *ResourceBuilderFlags) ToBuilder(restClientGetter RESTClientGetter, reso
} }
} }
if o.IncludeUninitialized != nil {
builder.IncludeUninitialized(*o.IncludeUninitialized)
}
if !o.StopOnFirstError { if !o.StopOnFirstError {
builder.ContinueOnError() builder.ContinueOnError()
} }

View File

@ -73,12 +73,11 @@ type Builder struct {
stream bool stream bool
dir bool dir bool
labelSelector *string labelSelector *string
fieldSelector *string fieldSelector *string
selectAll bool selectAll bool
includeUninitialized bool limitChunks int64
limitChunks int64 requestTransforms []RequestTransform
requestTransforms []RequestTransform
resources []string resources []string
@ -441,12 +440,6 @@ func (b *Builder) ExportParam(export bool) *Builder {
return b return b
} }
// IncludeUninitialized accepts the include-uninitialized boolean for these resources
func (b *Builder) IncludeUninitialized(includeUninitialized bool) *Builder {
b.includeUninitialized = includeUninitialized
return b
}
// NamespaceParam accepts the namespace that these resources should be // NamespaceParam accepts the namespace that these resources should be
// considered under from - used by DefaultNamespace() and RequireNamespace() // considered under from - used by DefaultNamespace() and RequireNamespace()
func (b *Builder) NamespaceParam(namespace string) *Builder { func (b *Builder) NamespaceParam(namespace string) *Builder {
@ -844,7 +837,7 @@ func (b *Builder) visitBySelector() *Result {
if mapping.Scope.Name() != meta.RESTScopeNameNamespace { if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = "" selectorNamespace = ""
} }
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.includeUninitialized, b.limitChunks)) visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.limitChunks))
} }
if b.continueOnError { if b.continueOnError {
result.visitor = EagerVisitorList(visitors) result.visitor = EagerVisitorList(visitors)

View File

@ -27,27 +27,25 @@ import (
// Selector is a Visitor for resources that match a label selector. // Selector is a Visitor for resources that match a label selector.
type Selector struct { type Selector struct {
Client RESTClient Client RESTClient
Mapping *meta.RESTMapping Mapping *meta.RESTMapping
Namespace string Namespace string
LabelSelector string LabelSelector string
FieldSelector string FieldSelector string
Export bool Export bool
IncludeUninitialized bool LimitChunks int64
LimitChunks int64
} }
// NewSelector creates a resource selector which hides details of getting items by their label selector. // NewSelector creates a resource selector which hides details of getting items by their label selector.
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector { func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export bool, limitChunks int64) *Selector {
return &Selector{ return &Selector{
Client: client, Client: client,
Mapping: mapping, Mapping: mapping,
Namespace: namespace, Namespace: namespace,
LabelSelector: labelSelector, LabelSelector: labelSelector,
FieldSelector: fieldSelector, FieldSelector: fieldSelector,
Export: export, Export: export,
IncludeUninitialized: includeUninitialized, LimitChunks: limitChunks,
LimitChunks: limitChunks,
} }
} }
@ -60,11 +58,10 @@ func (r *Selector) Visit(fn VisitorFunc) error {
r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
r.Export, r.Export,
&metav1.ListOptions{ &metav1.ListOptions{
LabelSelector: r.LabelSelector, LabelSelector: r.LabelSelector,
FieldSelector: r.FieldSelector, FieldSelector: r.FieldSelector,
IncludeUninitialized: r.IncludeUninitialized, Limit: r.LimitChunks,
Limit: r.LimitChunks, Continue: continueToken,
Continue: continueToken,
}, },
) )
if err != nil { if err != nil {

View File

@ -118,12 +118,12 @@ func (apiServerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runt
return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService))
} }
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
apiserver, ok := obj.(*apiregistration.APIService) apiserver, ok := obj.(*apiregistration.APIService)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a APIService.") return nil, nil, fmt.Errorf("given object is not a APIService.")
} }
return labels.Set(apiserver.ObjectMeta.Labels), APIServiceToSelectableFields(apiserver), apiserver.Initializers != nil, nil return labels.Set(apiserver.ObjectMeta.Labels), APIServiceToSelectableFields(apiserver), nil
} }
// MatchAPIService is the filter used by the generic etcd backend to watch events // MatchAPIService is the filter used by the generic etcd backend to watch events

View File

@ -38,12 +38,12 @@ func NewStrategy(typer runtime.ObjectTyper) fischerStrategy {
// GetAttrs returns labels.Set, fields.Set, the presence of Initializers if any // GetAttrs returns labels.Set, fields.Set, the presence of Initializers if any
// and error in case the given runtime.Object is not a Fischer // and error in case the given runtime.Object is not a Fischer
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
apiserver, ok := obj.(*wardle.Fischer) apiserver, ok := obj.(*wardle.Fischer)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a Fischer") return nil, nil, fmt.Errorf("given object is not a Fischer")
} }
return labels.Set(apiserver.ObjectMeta.Labels), SelectableFields(apiserver), apiserver.Initializers != nil, nil return labels.Set(apiserver.ObjectMeta.Labels), SelectableFields(apiserver), nil
} }
// MatchFischer is the filter used by the generic etcd backend to watch events // MatchFischer is the filter used by the generic etcd backend to watch events

View File

@ -39,12 +39,12 @@ func NewStrategy(typer runtime.ObjectTyper) flunderStrategy {
// GetAttrs returns labels.Set, fields.Set, the presence of Initializers if any // GetAttrs returns labels.Set, fields.Set, the presence of Initializers if any
// and error in case the given runtime.Object is not a Flunder // and error in case the given runtime.Object is not a Flunder
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
apiserver, ok := obj.(*wardle.Flunder) apiserver, ok := obj.(*wardle.Flunder)
if !ok { if !ok {
return nil, nil, false, fmt.Errorf("given object is not a Flunder") return nil, nil, fmt.Errorf("given object is not a Flunder")
} }
return labels.Set(apiserver.ObjectMeta.Labels), SelectableFields(apiserver), apiserver.Initializers != nil, nil return labels.Set(apiserver.ObjectMeta.Labels), SelectableFields(apiserver), nil
} }
// MatchFlunder is the filter used by the generic etcd backend to watch events // MatchFlunder is the filter used by the generic etcd backend to watch events

View File

@ -86,7 +86,7 @@ func waitForPodInNamespace(c clientset.Interface, ns, podName string) *v1.Pod {
var pod *v1.Pod var pod *v1.Pod
var err error var err error
err = wait.PollImmediate(2*time.Second, 15*time.Second, func() (bool, error) { err = wait.PollImmediate(2*time.Second, 15*time.Second, func() (bool, error) {
pod, err = c.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{IncludeUninitialized: true}) pod, err = c.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{})
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return false, nil return false, nil
} }
@ -130,28 +130,6 @@ func ensurePodsAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) {
By("Waiting for the pod to have running status") By("Waiting for the pod to have running status")
framework.ExpectNoError(framework.WaitForPodRunningInNamespace(f.ClientSet, pod)) framework.ExpectNoError(framework.WaitForPodRunningInNamespace(f.ClientSet, pod))
By("Creating an uninitialized pod in the namespace")
podB := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-uninitialized",
Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "test.initializer.k8s.io"}}},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "nginx",
Image: imageutils.GetPauseImageName(),
},
},
},
}
go func() {
_, err = f.ClientSet.CoreV1().Pods(namespace.Name).Create(podB)
// This error is ok, because we will delete the pod before it completes initialization
framework.Logf("error from create uninitialized namespace: %v", err)
}()
podB = waitForPodInNamespace(f.ClientSet, namespace.Name, podB.Name)
By("Deleting the namespace") By("Deleting the namespace")
err = f.ClientSet.CoreV1().Namespaces().Delete(namespace.Name, nil) err = f.ClientSet.CoreV1().Namespaces().Delete(namespace.Name, nil)
Expect(err).NotTo(HaveOccurred(), "failed to delete namespace: %s", namespace.Name) Expect(err).NotTo(HaveOccurred(), "failed to delete namespace: %s", namespace.Name)
@ -174,8 +152,6 @@ func ensurePodsAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) {
By("Verifying there are no pods in the namespace") By("Verifying there are no pods in the namespace")
_, err = f.ClientSet.CoreV1().Pods(namespace.Name).Get(pod.Name, metav1.GetOptions{}) _, err = f.ClientSet.CoreV1().Pods(namespace.Name).Get(pod.Name, metav1.GetOptions{})
Expect(err).To(HaveOccurred(), "failed to get pod %s in namespace: %s", pod.Name, namespace.Name) Expect(err).To(HaveOccurred(), "failed to get pod %s in namespace: %s", pod.Name, namespace.Name)
_, err = f.ClientSet.CoreV1().Pods(namespace.Name).Get(podB.Name, metav1.GetOptions{IncludeUninitialized: true})
Expect(err).To(HaveOccurred(), "failed to get pod %s in namespace: %s", podB.Name, namespace.Name)
} }
func ensureServicesAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) { func ensureServicesAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) {

View File

@ -153,132 +153,6 @@ var _ = SIGDescribe("ResourceQuota", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("[Feature:Initializers] should create a ResourceQuota and capture the life of an uninitialized pod.", func() {
By("Creating a ResourceQuota")
quotaName := "test-quota"
resourceQuota := newTestResourceQuota(quotaName)
resourceQuota, err := createResourceQuota(f.ClientSet, f.Namespace.Name, resourceQuota)
Expect(err).NotTo(HaveOccurred())
By("Ensuring resource quota status is calculated")
usedResources := v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Creating an uninitialized Pod that fits quota")
podName := "test-pod"
requests := v1.ResourceList{}
requests[v1.ResourceCPU] = resource.MustParse("500m")
requests[v1.ResourceMemory] = resource.MustParse("252Mi")
pod := newTestPodForQuota(f, podName, requests, v1.ResourceList{})
pod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "unhandled"}}}
pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
// because no one is handling the initializer, server will return a 504 timeout
if err != nil && !errors.IsTimeout(err) {
framework.Failf("expect err to be timeout error, got %v", err)
}
createdPod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(podName, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
By("Ensuring only pod count is charged")
usedResources = v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Ensuring an uninitialized pod can update its resource requirements")
// a pod cannot dynamically update its resource requirements.
requests = v1.ResourceList{}
requests[v1.ResourceCPU] = resource.MustParse("100m")
requests[v1.ResourceMemory] = resource.MustParse("100Mi")
_, err = framework.UpdatePodWithRetries(f.ClientSet, f.Namespace.Name, createdPod.Name, func(p *v1.Pod) {
p.Spec.Containers[0].Resources.Requests = requests
})
Expect(err).NotTo(HaveOccurred())
By("Ensuring ResourceQuota status doesn't change")
usedResources = v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Allowing initializing a Pod that fits quota")
_, err = framework.UpdatePodWithRetries(f.ClientSet, f.Namespace.Name, createdPod.Name, func(p *v1.Pod) {
p.Initializers = nil
})
Expect(err).NotTo(HaveOccurred())
By("Ensuring ResourceQuota status captures the usage of the intialized pod")
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("1")
usedResources[v1.ResourceCPU] = requests[v1.ResourceCPU]
usedResources[v1.ResourceMemory] = requests[v1.ResourceMemory]
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Deleting the pod")
err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(createdPod.Name, metav1.NewDeleteOptions(0))
Expect(err).NotTo(HaveOccurred())
By("Ensuring resource quota status released the pod usage")
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("0")
usedResources[v1.ResourceCPU] = resource.MustParse("0")
usedResources[v1.ResourceMemory] = resource.MustParse("0")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Allowing creating an uninitialized pod that exceeds remaining quota")
requests = v1.ResourceList{}
requests[v1.ResourceCPU] = resource.MustParse("1100m")
requests[v1.ResourceMemory] = resource.MustParse("100Mi")
podName = "too-large-pod"
pod = newTestPodForQuota(f, podName, requests, v1.ResourceList{})
pod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "unhandled"}}}
_, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
// because no one is handling the initializer, server will return a 504 timeout
if err != nil && !errors.IsTimeout(err) {
framework.Failf("expect err to be timeout error, got %v", err)
}
createdPod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(podName, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
By("Ensuring only charges pod count")
usedResources = v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Disallowing initializing a Pod that doesn't fit quota")
_, err = framework.UpdatePodWithRetries(f.ClientSet, f.Namespace.Name, createdPod.Name, func(p *v1.Pod) {
p.Initializers = nil
})
Expect(err).To(HaveOccurred())
By("Ensuring ResourceQuota status doesn't change")
usedResources = v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
usedResources[v1.ResourcePods] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
By("Deleting the pod")
err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(createdPod.Name, metav1.NewDeleteOptions(0))
Expect(err).NotTo(HaveOccurred())
By("Ensuring ResourceQuota status doesn't change")
usedResources = v1.ResourceList{}
usedResources[v1.ResourceQuotas] = resource.MustParse("1")
// TODO: This is a bug. We need 51247 to fix it.
usedResources[v1.ResourcePods] = resource.MustParse("1")
err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
Expect(err).NotTo(HaveOccurred())
})
It("should create a ResourceQuota and capture the life of a pod.", func() { It("should create a ResourceQuota and capture the life of a pod.", func() {
By("Creating a ResourceQuota") By("Creating a ResourceQuota")
quotaName := "test-quota" quotaName := "test-quota"

View File

@ -27,9 +27,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
@ -96,9 +93,7 @@ func TestCRDShadowGroup(t *testing.T) {
} }
func TestCRD(t *testing.T) { func TestCRD(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)() result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--admission-control", "Initializers"}, framework.SharedEtcd())
defer result.TearDownFn() defer result.TearDownFn()
apiextensionsclient, err := apiextensionsclientset.NewForConfig(result.ClientConfig) apiextensionsclient, err := apiextensionsclientset.NewForConfig(result.ClientConfig)