Merge pull request #72972 from liggitt/remove-alpha-initializers

Remove use of alpha initializers

Kubernetes-commit: e28c757e8758638811130848abe7a47f760057c0
This commit is contained in:
Kubernetes Publisher 2019-01-24 14:54:52 -08:00
commit 8d018a8393
25 changed files with 389 additions and 1818 deletions

636
Godeps/Godeps.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,88 +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 configuration
import (
"fmt"
"reflect"
"sort"
"k8s.io/klog"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
type InitializerConfigurationLister interface {
List(opts metav1.ListOptions) (*v1alpha1.InitializerConfigurationList, error)
}
type InitializerConfigurationManager struct {
*poller
}
func NewInitializerConfigurationManager(c InitializerConfigurationLister) *InitializerConfigurationManager {
getFn := func() (runtime.Object, error) {
list, err := c.List(metav1.ListOptions{})
if err != nil {
if errors.IsNotFound(err) || errors.IsForbidden(err) {
klog.V(5).Infof("Initializers are disabled due to an error: %v", err)
return nil, ErrDisabled
}
return nil, err
}
return mergeInitializerConfigurations(list), nil
}
return &InitializerConfigurationManager{
newPoller(getFn),
}
}
// Initializers returns the merged InitializerConfiguration.
func (im *InitializerConfigurationManager) Initializers() (*v1alpha1.InitializerConfiguration, error) {
configuration, err := im.poller.configuration()
if err != nil {
return nil, err
}
initializerConfiguration, ok := configuration.(*v1alpha1.InitializerConfiguration)
if !ok {
return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(initializerConfiguration), reflect.TypeOf(configuration))
}
return initializerConfiguration, nil
}
func (im *InitializerConfigurationManager) Run(stopCh <-chan struct{}) {
im.poller.Run(stopCh)
}
func mergeInitializerConfigurations(initializerConfigurationList *v1alpha1.InitializerConfigurationList) *v1alpha1.InitializerConfiguration {
configurations := initializerConfigurationList.Items
sort.SliceStable(configurations, InitializerConfigurationSorter(configurations).ByName)
var ret v1alpha1.InitializerConfiguration
for _, c := range configurations {
ret.Initializers = append(ret.Initializers, c.Initializers...)
}
return &ret
}
type InitializerConfigurationSorter []v1alpha1.InitializerConfiguration
func (a InitializerConfigurationSorter) ByName(i, j int) bool {
return a[i].Name < a[j].Name
}

View File

@ -1,182 +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 configuration
import (
"fmt"
"reflect"
"testing"
"time"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type mockLister struct {
invoked int
successes int
failures int
configurationList v1alpha1.InitializerConfigurationList
t *testing.T
}
func newMockLister(successes, failures int, configurationList v1alpha1.InitializerConfigurationList, t *testing.T) *mockLister {
return &mockLister{
failures: failures,
successes: successes,
configurationList: configurationList,
t: t,
}
}
// The first List will be successful; the next m.failures List will
// fail; the next m.successes List will be successful
// List should only be called 1+m.failures+m.successes times.
func (m *mockLister) List(options metav1.ListOptions) (*v1alpha1.InitializerConfigurationList, error) {
m.invoked++
if m.invoked == 1 {
return &m.configurationList, nil
}
if m.invoked <= 1+m.failures {
return nil, fmt.Errorf("some error")
}
if m.invoked <= 1+m.failures+m.successes {
return &m.configurationList, nil
}
m.t.Fatalf("unexpected call to List, should only be called %d times", 1+m.successes+m.failures)
return nil, nil
}
var _ InitializerConfigurationLister = &mockLister{}
func TestConfiguration(t *testing.T) {
cases := []struct {
name string
failures int
// note that the first call to mockLister is always a success.
successes int
expectReady bool
}{
{
name: "number of failures hasn't reached failureThreshold",
failures: defaultFailureThreshold - 1,
expectReady: true,
},
{
name: "number of failures just reaches failureThreshold",
failures: defaultFailureThreshold,
expectReady: false,
},
{
name: "number of failures exceeds failureThreshold",
failures: defaultFailureThreshold + 1,
expectReady: false,
},
{
name: "number of failures exceeds failureThreshold, but then get another success",
failures: defaultFailureThreshold + 1,
successes: 1,
expectReady: true,
},
}
for _, c := range cases {
mock := newMockLister(c.successes, c.failures, v1alpha1.InitializerConfigurationList{}, t)
manager := NewInitializerConfigurationManager(mock)
manager.interval = 1 * time.Millisecond
for i := 0; i < 1+c.successes+c.failures; i++ {
manager.sync()
}
_, err := manager.Initializers()
if err != nil && c.expectReady {
t.Errorf("case %s, expect ready, got: %v", c.name, err)
}
if err == nil && !c.expectReady {
t.Errorf("case %s, expect not ready", c.name)
}
}
}
func TestMergeInitializerConfigurations(t *testing.T) {
configurationsList := v1alpha1.InitializerConfigurationList{
Items: []v1alpha1.InitializerConfiguration{
{
ObjectMeta: metav1.ObjectMeta{
Name: "provider_2",
},
Initializers: []v1alpha1.Initializer{
{
Name: "initializer_a",
},
{
Name: "initializer_b",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "provider_1",
},
Initializers: []v1alpha1.Initializer{
{
Name: "initializer_c",
},
{
Name: "initializer_d",
},
},
},
},
}
expected := &v1alpha1.InitializerConfiguration{
Initializers: []v1alpha1.Initializer{
{
Name: "initializer_c",
},
{
Name: "initializer_d",
},
{
Name: "initializer_a",
},
{
Name: "initializer_b",
},
},
}
got := mergeInitializerConfigurations(&configurationsList)
if !reflect.DeepEqual(got, expected) {
t.Errorf("expected: %#v, got: %#v", expected, got)
}
}
type disabledInitializerConfigLister struct{}
func (l *disabledInitializerConfigLister) List(options metav1.ListOptions) (*v1alpha1.InitializerConfigurationList, error) {
return nil, errors.NewNotFound(schema.GroupResource{Group: "admissionregistration", Resource: "initializerConfigurations"}, "")
}
func TestInitializerConfigDisabled(t *testing.T) {
manager := NewInitializerConfigurationManager(&disabledInitializerConfigLister{})
manager.sync()
_, err := manager.Initializers()
if err.Error() != ErrDisabled.Error() {
t.Errorf("expected %v, got %v", ErrDisabled, err)
}
}

View File

@ -1,369 +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 (
"fmt"
"io"
"strings"
"k8s.io/klog"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/configuration"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
)
const (
// Name of admission plug-in
PluginName = "Initializers"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewInitializer(), nil
})
}
type initializerOptions struct {
Initializers []string
}
// InitializationConfig specifies initialization config
type InitializationConfig interface {
Run(stopCh <-chan struct{})
Initializers() (*v1alpha1.InitializerConfiguration, error)
}
type initializer struct {
config InitializationConfig
authorizer authorizer.Authorizer
}
// NewInitializer creates a new initializer plugin which assigns newly created resources initializers
// based on configuration loaded from the admission API group.
// FUTURE: this may be moved to the storage layer of the apiserver, but for now this is an alpha feature
// that can be disabled.
func NewInitializer() admission.Interface {
return &initializer{}
}
// ValidateInitialization implements the InitializationValidator interface.
func (i *initializer) ValidateInitialization() error {
if i.config == nil {
return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided")
}
if i.authorizer == nil {
return fmt.Errorf("the Initializer admission plugin requires an authorizer to be provided")
}
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
if err := utilfeature.DefaultMutableFeatureGate.Set(string(features.Initializers) + "=true"); err != nil {
klog.Errorf("error enabling Initializers feature as part of admission plugin setup: %v", err)
} else {
klog.Infof("enabled Initializers feature as part of admission plugin setup")
}
}
i.config.Run(wait.NeverStop)
return nil
}
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (i *initializer) SetExternalKubeClientSet(client clientset.Interface) {
i.config = configuration.NewInitializerConfigurationManager(client.AdmissionregistrationV1alpha1().InitializerConfigurations())
}
// SetAuthorizer implements the WantsAuthorizer interface.
func (i *initializer) SetAuthorizer(a authorizer.Authorizer) {
i.authorizer = a
}
var initializerFieldPath = field.NewPath("metadata", "initializers")
// readConfig holds requests instead of failing them if the server is not yet initialized
// or is unresponsive. It formats the returned error for client use if necessary.
func (i *initializer) readConfig(a admission.Attributes) (*v1alpha1.InitializerConfiguration, error) {
// read initializers from config
config, err := i.config.Initializers()
if err == nil {
return config, nil
}
// if initializer configuration is disabled, fail open
if err == configuration.ErrDisabled {
return &v1alpha1.InitializerConfiguration{}, nil
}
e := errors.NewServerTimeout(a.GetResource().GroupResource(), "create", 1)
if err == configuration.ErrNotReady {
e.ErrStatus.Message = fmt.Sprintf("Waiting for initialization configuration to load: %v", err)
e.ErrStatus.Reason = "LoadingConfiguration"
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
Type: "InitializerConfigurationPending",
Message: "The server is waiting for the initializer configuration to be loaded.",
})
} else {
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the initializer configuration: %v", err)
e.ErrStatus.Reason = "LoadingConfiguration"
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
Type: "InitializerConfigurationFailure",
Message: "An error has occurred while refreshing the initializer configuration, no resources can be created until a refresh succeeds.",
})
}
return nil, e
}
// Admit checks for create requests to add initializers, or update request to enforce invariants.
// The admission controller fails open if the object doesn't have ObjectMeta (can't be initialized).
// A client with sufficient permission ("initialize" verb on resource) can specify its own initializers
// or an empty initializers struct (which bypasses initialization). Only clients with the initialize verb
// can update objects that have not completed initialization. Sub resources can still be modified on
// resources that are undergoing initialization.
// TODO: once this logic is ready for beta, move it into the REST storage layer.
func (i *initializer) Admit(a admission.Attributes) (err error) {
switch a.GetOperation() {
case admission.Create, admission.Update:
default:
return nil
}
// TODO: should sub-resource action should be denied until the object is initialized?
if len(a.GetSubresource()) > 0 {
return nil
}
switch a.GetOperation() {
case admission.Create:
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
// via our API that don't have ObjectMeta
return nil
}
existing := accessor.GetInitializers()
if existing != nil {
klog.V(5).Infof("Admin bypassing initialization for %s", a.GetResource())
// it must be possible for some users to bypass initialization - for now, check the initialize operation
if err := i.canInitialize(a, "create with initializers denied"); err != nil {
return err
}
// allow administrators to bypass initialization by setting an empty initializers struct
if len(existing.Pending) == 0 && existing.Result == nil {
accessor.SetInitializers(nil)
return nil
}
} else {
klog.V(5).Infof("Checking initialization for %s", a.GetResource())
config, err := i.readConfig(a)
if err != nil {
return err
}
// Mirror pods are exempt from initialization because they are created and initialized
// on the Kubelet before they appear in the API.
// TODO: once this moves to REST storage layer, this becomes a pod specific concern
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Pod").GroupKind() {
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
return err
}
annotations := accessor.GetAnnotations()
if _, isMirror := annotations[v1.MirrorPodAnnotationKey]; isMirror {
return nil
}
}
names := findInitializers(config, a.GetResource())
if len(names) == 0 {
klog.V(5).Infof("No initializers needed")
return nil
}
klog.V(5).Infof("Found initializers for %s: %v", a.GetResource(), names)
accessor.SetInitializers(newInitializers(names))
}
case admission.Update:
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
// via our API that don't have ObjectMeta
return nil
}
updated := accessor.GetInitializers()
// controllers deployed with an empty initializers.pending have their initializers set to nil
// but should be able to update without changing their manifest
if updated != nil && len(updated.Pending) == 0 && updated.Result == nil {
accessor.SetInitializers(nil)
updated = nil
}
existingAccessor, err := meta.Accessor(a.GetOldObject())
if err != nil {
// if the old object does not have an accessor, but the new one does, error out
return fmt.Errorf("initialized resources must be able to set initializers (%T): %v", a.GetOldObject(), err)
}
existing := existingAccessor.GetInitializers()
// updates on initialized resources are allowed
if updated == nil && existing == nil {
return nil
}
klog.V(5).Infof("Modifying uninitialized resource %s", a.GetResource())
// because we are called before validation, we need to ensure the update transition is valid.
if errs := validation.ValidateInitializersUpdate(updated, existing, initializerFieldPath); len(errs) > 0 {
return errors.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs)
}
// caller must have the ability to mutate un-initialized resources
if err := i.canInitialize(a, "update to uninitialized resource denied"); err != nil {
return err
}
// TODO: restrict initialization list changes to specific clients?
}
return nil
}
func (i *initializer) canInitialize(a admission.Attributes, message string) error {
// caller must have the ability to mutate un-initialized resources
decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{
Name: a.GetName(),
ResourceRequest: true,
User: a.GetUserInfo(),
Verb: "initialize",
Namespace: a.GetNamespace(),
APIGroup: a.GetResource().Group,
APIVersion: a.GetResource().Version,
Resource: a.GetResource().Resource,
})
if err != nil {
return err
}
if decision != authorizer.DecisionAllow {
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason))
}
return nil
}
// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
func (i *initializer) Handles(op admission.Operation) bool {
return op == admission.Create || op == admission.Update
}
// newInitializers populates an Initializers struct.
func newInitializers(names []string) *metav1.Initializers {
if len(names) == 0 {
return nil
}
var init []metav1.Initializer
for _, name := range names {
init = append(init, metav1.Initializer{Name: name})
}
return &metav1.Initializers{
Pending: init,
}
}
// findInitializers returns the list of initializer names that apply to a config. It returns an empty list
// if no initializers apply.
func findInitializers(initializers *v1alpha1.InitializerConfiguration, gvr schema.GroupVersionResource) []string {
var names []string
for _, init := range initializers.Initializers {
if !matchRule(init.Rules, gvr) {
continue
}
names = append(names, init.Name)
}
return names
}
// matchRule returns true if any rule matches the provided group version resource.
func matchRule(rules []v1alpha1.Rule, gvr schema.GroupVersionResource) bool {
for _, rule := range rules {
if !hasGroup(rule.APIGroups, gvr.Group) {
return false
}
if !hasVersion(rule.APIVersions, gvr.Version) {
return false
}
if !hasResource(rule.Resources, gvr.Resource) {
return false
}
}
return len(rules) > 0
}
func hasGroup(groups []string, group string) bool {
if groups[0] == "*" {
return true
}
for _, g := range groups {
if g == group {
return true
}
}
return false
}
func hasVersion(versions []string, version string) bool {
if versions[0] == "*" {
return true
}
for _, v := range versions {
if v == version {
return true
}
}
return false
}
func hasResource(resources []string, resource string) bool {
if resources[0] == "*" || resources[0] == "*/*" {
return true
}
for _, r := range resources {
if strings.Contains(r, "/") {
continue
}
if r == resource {
return true
}
}
return false
}

View File

@ -1,194 +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 (
"reflect"
"strings"
"testing"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
func newInitializer(name string, rules ...v1alpha1.Rule) *v1alpha1.InitializerConfiguration {
return addInitializer(&v1alpha1.InitializerConfiguration{}, name, rules...)
}
func addInitializer(base *v1alpha1.InitializerConfiguration, name string, rules ...v1alpha1.Rule) *v1alpha1.InitializerConfiguration {
base.Initializers = append(base.Initializers, v1alpha1.Initializer{
Name: name,
Rules: rules,
})
return base
}
func TestFindInitializers(t *testing.T) {
type args struct {
initializers *v1alpha1.InitializerConfiguration
gvr schema.GroupVersionResource
}
tests := []struct {
name string
args args
want []string
}{
{
name: "empty",
args: args{
gvr: schema.GroupVersionResource{},
initializers: newInitializer("1"),
},
},
{
name: "everything",
args: args{
gvr: schema.GroupVersionResource{},
initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}}),
},
want: []string{"1"},
},
{
name: "empty group",
args: args{
gvr: schema.GroupVersionResource{},
initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"*"}}),
},
want: []string{"1"},
},
{
name: "pod",
args: args{
gvr: schema.GroupVersionResource{Resource: "pods"},
initializers: addInitializer(
newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}}),
"2", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}},
),
},
want: []string{"1", "2"},
},
{
name: "multiple matches",
args: args{
gvr: schema.GroupVersionResource{Resource: "pods"},
initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}}),
},
want: []string{"1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := findInitializers(tt.args.initializers, tt.args.gvr); !reflect.DeepEqual(got, tt.want) {
t.Errorf("findInitializers() = %v, want %v", got, tt.want)
}
})
}
}
type fakeAuthorizer struct {
accept bool
}
func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if f.accept {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "denied", nil
}
func TestAdmitUpdate(t *testing.T) {
tests := []struct {
name string
oldInitializers *metav1.Initializers
newInitializers *metav1.Initializers
verifyUpdatedObj func(runtime.Object) (pass bool, reason string)
err string
}{
{
name: "updates on initialized resources are allowed",
oldInitializers: nil,
newInitializers: nil,
err: "",
},
{
name: "updates on initialized resources are allowed",
oldInitializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}},
newInitializers: &metav1.Initializers{},
verifyUpdatedObj: func(obj runtime.Object) (bool, string) {
accessor, err := meta.Accessor(obj)
if err != nil {
return false, "cannot get accessor"
}
if accessor.GetInitializers() != nil {
return false, "expect nil initializers"
}
return true, ""
},
err: "",
},
{
name: "initializers may not be set once initialized",
oldInitializers: nil,
newInitializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}},
err: "field is immutable once initialization has completed",
},
{
name: "empty initializer list is treated as nil initializer",
oldInitializers: nil,
newInitializers: &metav1.Initializers{},
verifyUpdatedObj: func(obj runtime.Object) (bool, string) {
accessor, err := meta.Accessor(obj)
if err != nil {
return false, "cannot get accessor"
}
if accessor.GetInitializers() != nil {
return false, "expect nil initializers"
}
return true, ""
},
err: "",
},
}
plugin := initializer{
config: nil,
authorizer: &fakeAuthorizer{true},
}
for _, tc := range tests {
oldObj := &v1.Pod{}
oldObj.Initializers = tc.oldInitializers
newObj := &v1.Pod{}
newObj.Initializers = tc.newInitializers
a := admission.NewAttributesRecord(newObj, oldObj, schema.GroupVersionKind{}, "", "foo", schema.GroupVersionResource{}, "", admission.Update, false, nil)
err := plugin.Admit(a)
switch {
case tc.err == "" && err != nil:
t.Errorf("%q: unexpected error: %v", tc.name, err)
case tc.err != "" && err == nil:
t.Errorf("%q: unexpected no error, expected %s", tc.name, tc.err)
case tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err):
t.Errorf("%q: expected %s, got %v", tc.name, tc.err, err)
}
}
}

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,8 @@ 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 // Initializers are a deprecated alpha field and should not be saved
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,11 +102,9 @@ 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 // Initializers are a deprecated alpha field and should not be saved
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)

View File

@ -28,7 +28,6 @@ import (
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/initializer"
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
"k8s.io/apiserver/pkg/admission/plugin/initialization"
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle" "k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating" mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating" validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
@ -79,8 +78,8 @@ func NewAdmissionOptions() *AdmissionOptions {
// admission plugins. The apiserver always runs the validating ones // admission plugins. The apiserver always runs the validating ones
// after all the mutating ones, so their relative order in this list // after all the mutating ones, so their relative order in this list
// doesn't matter. // doesn't matter.
RecommendedPluginOrder: []string{lifecycle.PluginName, initialization.PluginName, mutatingwebhook.PluginName, validatingwebhook.PluginName}, RecommendedPluginOrder: []string{lifecycle.PluginName, mutatingwebhook.PluginName, validatingwebhook.PluginName},
DefaultOffPlugins: sets.NewString(initialization.PluginName), DefaultOffPlugins: sets.NewString(),
} }
server.RegisterAllAdmissionPlugins(options.Plugins) server.RegisterAllAdmissionPlugins(options.Plugins)
return options return options

View File

@ -19,7 +19,6 @@ package server
// This file exists to force the desired plugin implementations to be linked into genericapi pkg. // This file exists to force the desired plugin implementations to be linked into genericapi pkg.
import ( import (
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/initialization"
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle" "k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating" mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating" validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
@ -28,7 +27,6 @@ import (
// RegisterAllAdmissionPlugins registers all admission plugins // RegisterAllAdmissionPlugins registers all admission plugins
func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
lifecycle.Register(plugins) lifecycle.Register(plugins)
initialization.Register(plugins)
validatingwebhook.Register(plugins) validatingwebhook.Register(plugins)
mutatingwebhook.Register(plugins) mutatingwebhook.Register(plugins)
} }

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)