Merge pull request #72972 from liggitt/remove-alpha-initializers
Remove use of alpha initializers Kubernetes-commit: e28c757e8758638811130848abe7a47f760057c0
This commit is contained in:
commit
8d018a8393
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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("")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue