mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Merge pull request #81816 from jennybuckley/apply-cap-managers
[server-side apply] Cap the number of managedFields entries for updates at 10
This commit is contained in:
commit
a8e8e54f7a
@ -823,18 +823,18 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
|
|||||||
}
|
}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||||
reqScope := *requestScopes[v.Name]
|
reqScope := *requestScopes[v.Name]
|
||||||
fm, err := fieldmanager.NewCRDFieldManager(
|
reqScope.FieldManager, err = fieldmanager.NewDefaultCRDFieldManager(
|
||||||
openAPIModels,
|
openAPIModels,
|
||||||
reqScope.Convertor,
|
reqScope.Convertor,
|
||||||
reqScope.Defaulter,
|
reqScope.Defaulter,
|
||||||
reqScope.Kind.GroupVersion(),
|
reqScope.Creater,
|
||||||
|
reqScope.Kind,
|
||||||
reqScope.HubGroupVersion,
|
reqScope.HubGroupVersion,
|
||||||
*crd.Spec.PreserveUnknownFields,
|
*crd.Spec.PreserveUnknownFields,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reqScope.FieldManager = fieldmanager.NewSkipNonAppliedManager(fm, reqScope.Creater, reqScope.Kind)
|
|
||||||
requestScopes[v.Name] = &reqScope
|
requestScopes[v.Name] = &reqScope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"buildmanagerinfo.go",
|
||||||
|
"capmanagers.go",
|
||||||
"fieldmanager.go",
|
"fieldmanager.go",
|
||||||
"skipnonapplied.go",
|
"skipnonapplied.go",
|
||||||
|
"stripmeta.go",
|
||||||
|
"structuredmerge.go",
|
||||||
],
|
],
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
|
||||||
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
|
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
|
||||||
@ -45,6 +49,7 @@ filegroup(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"capmanagers_test.go",
|
||||||
"fieldmanager_test.go",
|
"fieldmanager_test.go",
|
||||||
"skipnonapplied_test.go",
|
"skipnonapplied_test.go",
|
||||||
],
|
],
|
||||||
@ -57,6 +62,7 @@ go_test(
|
|||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
@ -65,6 +71,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
|
||||||
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type buildManagerInfoManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &buildManagerInfoManager{}
|
||||||
|
|
||||||
|
// NewBuildManagerInfoManager creates a new Manager that converts the manager name into a unique identifier
|
||||||
|
// combining operation and version for update requests, and just operation for apply requests.
|
||||||
|
func NewBuildManagerInfoManager(f Manager, gv schema.GroupVersion) Manager {
|
||||||
|
return &buildManagerInfoManager{
|
||||||
|
fieldManager: f,
|
||||||
|
groupVersion: gv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *buildManagerInfoManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *buildManagerInfoManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationApply)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
return f.fieldManager.Apply(liveObj, patch, managed, manager, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
||||||
|
managerInfo := metav1.ManagedFieldsEntry{
|
||||||
|
Manager: prefix,
|
||||||
|
Operation: operation,
|
||||||
|
APIVersion: f.groupVersion.String(),
|
||||||
|
}
|
||||||
|
if managerInfo.Manager == "" {
|
||||||
|
managerInfo.Manager = "unknown"
|
||||||
|
}
|
||||||
|
return internal.BuildManagerIdentifier(&managerInfo)
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type capManagersManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
maxUpdateManagers int
|
||||||
|
oldUpdatesManagerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &capManagersManager{}
|
||||||
|
|
||||||
|
// NewCapManagersManager creates a new wrapped FieldManager which ensures that the number of managers from updates
|
||||||
|
// does not exceed maxUpdateManagers, by merging some of the oldest entries on each update.
|
||||||
|
func NewCapManagersManager(fieldManager Manager, maxUpdateManagers int) Manager {
|
||||||
|
return &capManagersManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
maxUpdateManagers: maxUpdateManagers,
|
||||||
|
oldUpdatesManagerName: "ancient-changes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *capManagersManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
if err != nil {
|
||||||
|
return object, managed, err
|
||||||
|
}
|
||||||
|
if managed, err = f.capUpdateManagers(managed); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to cap update managers: %v", err)
|
||||||
|
}
|
||||||
|
return object, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *capManagersManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
return f.fieldManager.Apply(liveObj, patch, managed, fieldManager, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
// capUpdateManagers merges a number of the oldest update entries into versioned buckets,
|
||||||
|
// such that the number of entries from updates does not exceed f.maxUpdateManagers.
|
||||||
|
func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Managed, err error) {
|
||||||
|
// Gather all entries from updates
|
||||||
|
updaters := []string{}
|
||||||
|
for manager, fields := range managed.Fields() {
|
||||||
|
if fields.Applied() == false {
|
||||||
|
updaters = append(updaters, manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(updaters) <= f.maxUpdateManagers {
|
||||||
|
return managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have more than the maximum, sort the update entries by time, oldest first.
|
||||||
|
sort.Slice(updaters, func(i, j int) bool {
|
||||||
|
iTime, jTime, nTime := managed.Times()[updaters[i]], managed.Times()[updaters[j]], &metav1.Time{Time: time.Time{}}
|
||||||
|
if iTime == nil {
|
||||||
|
iTime = nTime
|
||||||
|
}
|
||||||
|
if jTime == nil {
|
||||||
|
jTime = nTime
|
||||||
|
}
|
||||||
|
if !iTime.Equal(jTime) {
|
||||||
|
return iTime.Before(jTime)
|
||||||
|
}
|
||||||
|
return updaters[i] < updaters[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Merge the oldest updaters with versioned bucket managers until the number of updaters is under the cap
|
||||||
|
versionToFirstManager := map[string]string{}
|
||||||
|
for i, length := 0, len(updaters); i < len(updaters) && length > f.maxUpdateManagers; i++ {
|
||||||
|
manager := updaters[i]
|
||||||
|
vs := managed.Fields()[manager]
|
||||||
|
time := managed.Times()[manager]
|
||||||
|
version := string(vs.APIVersion())
|
||||||
|
|
||||||
|
// Create a new manager identifier for the versioned bucket entry.
|
||||||
|
// The version for this manager comes from the version of the update being merged into the bucket.
|
||||||
|
bucket, err := internal.BuildManagerIdentifier(&metav1.ManagedFieldsEntry{
|
||||||
|
Manager: f.oldUpdatesManagerName,
|
||||||
|
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||||
|
APIVersion: version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return managed, fmt.Errorf("failed to create bucket manager for version %v: %v", version, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the fieldets if this is not the first time the version was seen.
|
||||||
|
// Otherwise just record the manager name in versionToFirstManager
|
||||||
|
if first, ok := versionToFirstManager[version]; ok {
|
||||||
|
// If the bucket doesn't exists yet, create one.
|
||||||
|
if _, ok := managed.Fields()[bucket]; !ok {
|
||||||
|
s := managed.Fields()[first]
|
||||||
|
delete(managed.Fields(), first)
|
||||||
|
managed.Fields()[bucket] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
managed.Fields()[bucket] = fieldpath.NewVersionedSet(vs.Set().Union(managed.Fields()[bucket].Set()), vs.APIVersion(), vs.Applied())
|
||||||
|
delete(managed.Fields(), manager)
|
||||||
|
length--
|
||||||
|
|
||||||
|
// Use the time from the update being merged into the bucket, since it is more recent.
|
||||||
|
managed.Times()[bucket] = time
|
||||||
|
} else {
|
||||||
|
versionToFirstManager[version] = manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return managed, nil
|
||||||
|
}
|
@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeManager struct{}
|
||||||
|
|
||||||
|
var _ fieldmanager.Manager = &fakeManager{}
|
||||||
|
|
||||||
|
func (*fakeManager) Update(_, newObj runtime.Object, managed fieldmanager.Managed, _ string) (runtime.Object, fieldmanager.Managed, error) {
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*fakeManager) Apply(_ runtime.Object, _ []byte, _ fieldmanager.Managed, _ string, force bool) (runtime.Object, fieldmanager.Managed, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapManagersManagerMergesEntries(t *testing.T) {
|
||||||
|
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
|
||||||
|
f.fieldManager = fieldmanager.NewCapManagersManager(f.fieldManager, 3)
|
||||||
|
|
||||||
|
podWithLabels := func(labels ...string) runtime.Object {
|
||||||
|
labelMap := map[string]interface{}{}
|
||||||
|
for _, key := range labels {
|
||||||
|
labelMap[key] = "true"
|
||||||
|
}
|
||||||
|
obj := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": labelMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj.SetKind("Pod")
|
||||||
|
obj.SetAPIVersion("v1")
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Update(podWithLabels("one"), "fieldmanager_test_update_1"); err != nil {
|
||||||
|
t.Fatalf("failed to update object: %v", err)
|
||||||
|
}
|
||||||
|
expectIdempotence(t, f)
|
||||||
|
|
||||||
|
if err := f.Update(podWithLabels("one", "two"), "fieldmanager_test_update_2"); err != nil {
|
||||||
|
t.Fatalf("failed to update object: %v", err)
|
||||||
|
}
|
||||||
|
expectIdempotence(t, f)
|
||||||
|
|
||||||
|
if err := f.Update(podWithLabels("one", "two", "three"), "fieldmanager_test_update_3"); err != nil {
|
||||||
|
t.Fatalf("failed to update object: %v", err)
|
||||||
|
}
|
||||||
|
expectIdempotence(t, f)
|
||||||
|
|
||||||
|
if err := f.Update(podWithLabels("one", "two", "three", "four"), "fieldmanager_test_update_4"); err != nil {
|
||||||
|
t.Fatalf("failed to update object: %v", err)
|
||||||
|
}
|
||||||
|
expectIdempotence(t, f)
|
||||||
|
|
||||||
|
if e, a := 3, len(f.ManagedFields()); e != a {
|
||||||
|
t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := "ancient-changes", f.ManagedFields()[0].Manager; e != a {
|
||||||
|
t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := "fieldmanager_test_update_3", f.ManagedFields()[1].Manager; e != a {
|
||||||
|
t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := "fieldmanager_test_update_4", f.ManagedFields()[2].Manager; e != a {
|
||||||
|
t.Fatalf("exected third manager name to be %v, but got %v: %#v", e, a, f.ManagedFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "one"))
|
||||||
|
expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "two"))
|
||||||
|
expectManagesField(t, f, "fieldmanager_test_update_3", fieldpath.MakePathOrDie("metadata", "labels", "three"))
|
||||||
|
expectManagesField(t, f, "fieldmanager_test_update_4", fieldpath.MakePathOrDie("metadata", "labels", "four"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapUpdateManagers(t *testing.T) {
|
||||||
|
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
|
||||||
|
f.fieldManager = fieldmanager.NewCapManagersManager(&fakeManager{}, 3)
|
||||||
|
|
||||||
|
set := func(fields ...string) *metav1.FieldsV1 {
|
||||||
|
s := fieldpath.NewSet()
|
||||||
|
for _, f := range fields {
|
||||||
|
s.Insert(fieldpath.MakePathOrDie(f))
|
||||||
|
}
|
||||||
|
b, err := s.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error building ManagedFieldsEntry for test: %v", err))
|
||||||
|
}
|
||||||
|
return &metav1.FieldsV1{Raw: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := func(name string, version string, order int, fields *metav1.FieldsV1) metav1.ManagedFieldsEntry {
|
||||||
|
return metav1.ManagedFieldsEntry{
|
||||||
|
Manager: name,
|
||||||
|
APIVersion: version,
|
||||||
|
Operation: "Update",
|
||||||
|
FieldsType: "FieldsV1",
|
||||||
|
FieldsV1: fields,
|
||||||
|
Time: &metav1.Time{Time: time.Time{}.Add(time.Hour * time.Duration(order))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input []metav1.ManagedFieldsEntry
|
||||||
|
expected []metav1.ManagedFieldsEntry
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "one version, no ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("update-manager1", "v1", 1, set("a")),
|
||||||
|
entry("update-manager2", "v1", 2, set("b")),
|
||||||
|
entry("update-manager3", "v1", 3, set("c")),
|
||||||
|
entry("update-manager4", "v1", 4, set("d")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 2, set("a", "b")),
|
||||||
|
entry("update-manager3", "v1", 3, set("c")),
|
||||||
|
entry("update-manager4", "v1", 4, set("d")),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "one version, one ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 2, set("a", "b")),
|
||||||
|
entry("update-manager3", "v1", 3, set("c")),
|
||||||
|
entry("update-manager4", "v1", 4, set("d")),
|
||||||
|
entry("update-manager5", "v1", 5, set("e")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 3, set("a", "b", "c")),
|
||||||
|
entry("update-manager4", "v1", 4, set("d")),
|
||||||
|
entry("update-manager5", "v1", 5, set("e")),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "two versions, no ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("update-manager1", "v1", 1, set("a")),
|
||||||
|
entry("update-manager2", "v2", 2, set("b")),
|
||||||
|
entry("update-manager3", "v1", 3, set("c")),
|
||||||
|
entry("update-manager4", "v1", 4, set("d")),
|
||||||
|
entry("update-manager5", "v1", 5, set("e")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("update-manager2", "v2", 2, set("b")),
|
||||||
|
entry("ancient-changes", "v1", 4, set("a", "c", "d")),
|
||||||
|
entry("update-manager5", "v1", 5, set("e")),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "three versions, one ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("update-manager2", "v2", 2, set("b")),
|
||||||
|
entry("ancient-changes", "v1", 4, set("a", "c", "d")),
|
||||||
|
entry("update-manager5", "v1", 5, set("e")),
|
||||||
|
entry("update-manager6", "v3", 6, set("f")),
|
||||||
|
entry("update-manager7", "v2", 7, set("g")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")),
|
||||||
|
entry("update-manager6", "v3", 6, set("f")),
|
||||||
|
entry("ancient-changes", "v2", 7, set("b", "g")),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "three versions, two ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")),
|
||||||
|
entry("update-manager6", "v3", 6, set("f")),
|
||||||
|
entry("ancient-changes", "v2", 7, set("b", "g")),
|
||||||
|
entry("update-manager8", "v3", 8, set("h")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")),
|
||||||
|
entry("ancient-changes", "v2", 7, set("b", "g")),
|
||||||
|
entry("ancient-changes", "v3", 8, set("f", "h")),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "four versions, two ancient changes",
|
||||||
|
input: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")),
|
||||||
|
entry("update-manager6", "v3", 6, set("f")),
|
||||||
|
entry("ancient-changes", "v2", 7, set("b", "g")),
|
||||||
|
entry("update-manager8", "v4", 8, set("h")),
|
||||||
|
},
|
||||||
|
expected: []metav1.ManagedFieldsEntry{
|
||||||
|
entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")),
|
||||||
|
entry("update-manager6", "v3", 6, set("f")),
|
||||||
|
entry("ancient-changes", "v2", 7, set("b", "g")),
|
||||||
|
entry("update-manager8", "v4", 8, set("h")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
f.Reset()
|
||||||
|
accessor, err := meta.Accessor(f.liveObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v: couldn't get accessor: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
accessor.SetManagedFields(tc.input)
|
||||||
|
if err := f.Update(f.liveObj, "no-op-update"); err != nil {
|
||||||
|
t.Fatalf("%v: failed to do no-op update to object: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.expected, f.ManagedFields(); !apiequality.Semantic.DeepEqual(e, a) {
|
||||||
|
t.Errorf("%v: unexpected value for managedFields:\nexpected: %v\n but got: %v", tc.name, mustMarshal(e), mustMarshal(a))
|
||||||
|
}
|
||||||
|
expectIdempotence(t, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectIdempotence does a no-op update and ensures that managedFields doesn't change by calling capUpdateManagers.
|
||||||
|
func expectIdempotence(t *testing.T, f TestFieldManager) {
|
||||||
|
before := []metav1.ManagedFieldsEntry{}
|
||||||
|
for _, m := range f.ManagedFields() {
|
||||||
|
before = append(before, *m.DeepCopy())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Update(f.liveObj, "no-op-update"); err != nil {
|
||||||
|
t.Fatalf("failed to do no-op update to object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if after := f.ManagedFields(); !apiequality.Semantic.DeepEqual(before, after) {
|
||||||
|
t.Fatalf("exected idempotence, but managedFields changed:\nbefore: %v\n after: %v", mustMarshal(before), mustMarshal(after))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectManagesField ensures that manager m currently manages field path p.
|
||||||
|
func expectManagesField(t *testing.T, f TestFieldManager, m string, p fieldpath.Path) {
|
||||||
|
for _, e := range f.ManagedFields() {
|
||||||
|
if e.Manager == m {
|
||||||
|
var s fieldpath.Set
|
||||||
|
err := s.FromJSON(bytes.NewReader(e.FieldsV1.Raw))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing managedFields for %v: %v: %#v", m, err, f.ManagedFields())
|
||||||
|
}
|
||||||
|
if !s.Has(p) {
|
||||||
|
t.Fatalf("expected managedFields for %v to contain %v, but got:\n%v", m, p.String(), s.String())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("exected to find manager name %v, but got: %#v", m, f.ManagedFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustMarshal(i interface{}) string {
|
||||||
|
b, err := json.MarshalIndent(i, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error marshalling %v to json: %v", i, err))
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
@ -18,302 +18,144 @@ package fieldmanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
"k8s.io/klog"
|
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
"sigs.k8s.io/structured-merge-diff/merge"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FieldManager updates the managed fields and merge applied
|
// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates
|
||||||
// configurations.
|
// if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum.
|
||||||
type FieldManager interface {
|
// TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries.
|
||||||
|
const DefaultMaxUpdateManagers int = 10
|
||||||
|
|
||||||
|
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||||
|
type Managed interface {
|
||||||
|
// Fields gets the fieldpath.ManagedFields.
|
||||||
|
Fields() fieldpath.ManagedFields
|
||||||
|
|
||||||
|
// Times gets the timestamps associated with each operation.
|
||||||
|
Times() map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager updates the managed fields and merges applied configurations.
|
||||||
|
type Manager interface {
|
||||||
// Update is used when the object has already been merged (non-apply
|
// Update is used when the object has already been merged (non-apply
|
||||||
// use-case), and simply updates the managed fields in the output
|
// use-case), and simply updates the managed fields in the output
|
||||||
// object.
|
// object.
|
||||||
Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error)
|
Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error)
|
||||||
|
|
||||||
// Apply is used when server-side apply is called, as it merges the
|
// Apply is used when server-side apply is called, as it merges the
|
||||||
// object and update the managed fields.
|
// object and updates the managed fields.
|
||||||
Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error)
|
Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fieldManager struct {
|
// FieldManager updates the managed fields and merge applied
|
||||||
typeConverter internal.TypeConverter
|
// configurations.
|
||||||
objectConverter runtime.ObjectConvertor
|
type FieldManager struct {
|
||||||
objectDefaulter runtime.ObjectDefaulter
|
fieldManager Manager
|
||||||
groupVersion schema.GroupVersion
|
|
||||||
hubVersion schema.GroupVersion
|
|
||||||
updater merge.Updater
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ FieldManager = &fieldManager{}
|
// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields
|
||||||
|
// on update and apply requests.
|
||||||
|
func NewFieldManager(f Manager) *FieldManager {
|
||||||
|
return &FieldManager{f}
|
||||||
|
}
|
||||||
|
|
||||||
// NewFieldManager creates a new FieldManager that merges apply requests
|
// NewDefaultFieldManager creates a new FieldManager that merges apply requests
|
||||||
// and update managed fields for other types of requests.
|
// and update managed fields for other types of requests.
|
||||||
func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (FieldManager, error) {
|
func NewDefaultFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion) (*FieldManager, error) {
|
||||||
typeConverter, err := internal.NewTypeConverter(models, false)
|
f, err := NewStructuredMergeManager(models, objectConverter, objectDefaulter, kind.GroupVersion(), hub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||||
}
|
}
|
||||||
|
return newDefaultFieldManager(f, objectCreater, kind), nil
|
||||||
return &fieldManager{
|
|
||||||
typeConverter: typeConverter,
|
|
||||||
objectConverter: objectConverter,
|
|
||||||
objectDefaulter: objectDefaulter,
|
|
||||||
groupVersion: gv,
|
|
||||||
hubVersion: hub,
|
|
||||||
updater: merge.Updater{
|
|
||||||
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCRDFieldManager creates a new FieldManager specifically for
|
// NewDefaultCRDFieldManager creates a new FieldManager specifically for
|
||||||
// CRDs. This allows for the possibility of fields which are not defined
|
// CRDs. This allows for the possibility of fields which are not defined
|
||||||
// in models, as well as having no models defined at all.
|
// in models, as well as having no models defined at all.
|
||||||
func NewCRDFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, preserveUnknownFields bool) (_ FieldManager, err error) {
|
func NewDefaultCRDFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, preserveUnknownFields bool) (_ *FieldManager, err error) {
|
||||||
var typeConverter internal.TypeConverter = internal.DeducedTypeConverter{}
|
f, err := NewCRDStructuredMergeManager(models, objectConverter, objectDefaulter, kind.GroupVersion(), hub, preserveUnknownFields)
|
||||||
if models != nil {
|
if err != nil {
|
||||||
typeConverter, err = internal.NewTypeConverter(models, preserveUnknownFields)
|
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &fieldManager{
|
return newDefaultFieldManager(f, objectCreater, kind), nil
|
||||||
typeConverter: typeConverter,
|
|
||||||
objectConverter: objectConverter,
|
|
||||||
objectDefaulter: objectDefaulter,
|
|
||||||
groupVersion: gv,
|
|
||||||
hubVersion: hub,
|
|
||||||
updater: merge.Updater{
|
|
||||||
Converter: internal.NewCRDVersionConverter(typeConverter, objectConverter, hub),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update implements FieldManager.
|
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic.
|
||||||
func (f *fieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
|
func newDefaultFieldManager(f Manager, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind) *FieldManager {
|
||||||
|
f = NewStripMetaManager(f)
|
||||||
|
f = NewBuildManagerInfoManager(f, kind.GroupVersion())
|
||||||
|
f = NewCapManagersManager(f, DefaultMaxUpdateManagers)
|
||||||
|
f = NewSkipNonAppliedManager(f, objectCreater, kind)
|
||||||
|
return NewFieldManager(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is used when the object has already been merged (non-apply
|
||||||
|
// use-case), and simply updates the managed fields in the output
|
||||||
|
// object.
|
||||||
|
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) {
|
||||||
// If the object doesn't have metadata, we should just return without trying to
|
// If the object doesn't have metadata, we should just return without trying to
|
||||||
// set the managedFields at all, so creates/updates/patches will work normally.
|
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||||
if _, err := meta.Accessor(newObj); err != nil {
|
if _, err = meta.Accessor(newObj); err != nil {
|
||||||
return newObj, nil
|
return newObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// First try to decode the managed fields provided in the update,
|
// First try to decode the managed fields provided in the update,
|
||||||
// This is necessary to allow directly updating managed fields.
|
// This is necessary to allow directly updating managed fields.
|
||||||
managed, err := internal.DecodeObjectManagedFields(newObj)
|
var managed Managed
|
||||||
|
if managed, err = internal.DecodeObjectManagedFields(newObj); err != nil || len(managed.Fields()) == 0 {
|
||||||
// If the managed field is empty or we failed to decode it,
|
// If the managed field is empty or we failed to decode it,
|
||||||
// let's try the live object. This is to prevent clients who
|
// let's try the live object. This is to prevent clients who
|
||||||
// don't understand managedFields from deleting it accidentally.
|
// don't understand managedFields from deleting it accidentally.
|
||||||
if err != nil || len(managed.Fields) == 0 {
|
|
||||||
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newObjVersioned, err := f.toVersioned(newObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
|
||||||
}
|
|
||||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
|
||||||
}
|
|
||||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
|
||||||
internal.RemoveObjectManagedFields(newObjVersioned)
|
|
||||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
|
||||||
if err != nil {
|
|
||||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
|
||||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed new object: %v", err)
|
|
||||||
return newObj, nil
|
|
||||||
}
|
|
||||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
|
||||||
if err != nil {
|
|
||||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
|
||||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed live object: %v", err)
|
|
||||||
return newObj, nil
|
|
||||||
}
|
|
||||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
|
||||||
|
|
||||||
// TODO(apelisse) use the first return value when unions are implemented
|
internal.RemoveObjectManagedFields(liveObj)
|
||||||
_, managed.Fields, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields, manager)
|
internal.RemoveObjectManagedFields(newObj)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
|
||||||
}
|
|
||||||
managed.Fields = f.stripFields(managed.Fields, manager)
|
|
||||||
|
|
||||||
// If the current operation took any fields from anything, it means the object changed,
|
if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil {
|
||||||
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
|
return nil, err
|
||||||
if vs, ok := managed.Fields[manager]; ok {
|
|
||||||
delete(managed.Fields, manager)
|
|
||||||
|
|
||||||
// Build a manager identifier which will only match previous updates from the same manager
|
|
||||||
manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
managed.Times[manager] = &metav1.Time{Time: time.Now().UTC()}
|
|
||||||
if previous, ok := managed.Fields[manager]; ok {
|
|
||||||
managed.Fields[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
|
|
||||||
} else {
|
|
||||||
managed.Fields[manager] = vs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
|
||||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newObj, nil
|
return object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply implements FieldManager.
|
// Apply is used when server-side apply is called, as it merges the
|
||||||
func (f *fieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error) {
|
// object and updates the managed fields.
|
||||||
|
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, manager string, force bool) (object runtime.Object, err error) {
|
||||||
// If the object doesn't have metadata, apply isn't allowed.
|
// If the object doesn't have metadata, apply isn't allowed.
|
||||||
_, err := meta.Accessor(liveObj)
|
if _, err = meta.Accessor(liveObj); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
managed, err := internal.DecodeObjectManagedFields(liveObj)
|
// Decode the managed fields in the live object, since it isn't allowed in the patch.
|
||||||
if err != nil {
|
var managed Managed
|
||||||
|
if managed, err = internal.DecodeObjectManagedFields(liveObj); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||||
}
|
}
|
||||||
// Check that the patch object has the same version as the live object
|
|
||||||
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
internal.RemoveObjectManagedFields(liveObj)
|
||||||
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if patchObj.GetManagedFields() != nil {
|
if object, managed, err = f.fieldManager.Apply(liveObj, patch, managed, manager, force); err != nil {
|
||||||
return nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
|
||||||
return nil,
|
|
||||||
errors.NewBadRequest(
|
|
||||||
fmt.Sprintf("Incorrect version specified in apply patch. "+
|
|
||||||
"Specified patch version: %s, expected: %s",
|
|
||||||
patchObj.GetAPIVersion(), f.groupVersion.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
|
||||||
}
|
|
||||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
|
||||||
|
|
||||||
patchObjTyped, err := f.typeConverter.ObjectToTyped(patchObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create typed patch object: %v", err)
|
|
||||||
}
|
|
||||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
|
||||||
}
|
|
||||||
manager, err := f.buildManagerInfo(fieldManager, metav1.ManagedFieldsOperationApply)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
|
||||||
newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields, manager, force)
|
|
||||||
if err != nil {
|
|
||||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
|
||||||
return nil, internal.NewConflictError(conflicts)
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
managed.Fields = f.stripFields(managedFields, manager)
|
|
||||||
|
|
||||||
// Update the time in the managedFieldsEntry for this operation
|
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
|
||||||
managed.Times[manager] = &metav1.Time{Time: time.Now().UTC()}
|
|
||||||
|
|
||||||
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert new typed object to object: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newObjVersioned, err := f.toVersioned(newObj)
|
return object, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
|
||||||
}
|
|
||||||
f.objectDefaulter.Default(newObjVersioned)
|
|
||||||
|
|
||||||
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert to unversioned: %v", err)
|
|
||||||
}
|
|
||||||
return newObjUnversioned, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
|
||||||
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
|
||||||
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fieldManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
|
||||||
managerInfo := metav1.ManagedFieldsEntry{
|
|
||||||
Manager: prefix,
|
|
||||||
Operation: operation,
|
|
||||||
APIVersion: f.groupVersion.String(),
|
|
||||||
}
|
|
||||||
if managerInfo.Manager == "" {
|
|
||||||
managerInfo.Manager = "unknown"
|
|
||||||
}
|
|
||||||
return internal.BuildManagerIdentifier(&managerInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripSet is the list of fields that should never be part of a mangedFields.
|
|
||||||
var stripSet = fieldpath.NewSet(
|
|
||||||
fieldpath.MakePathOrDie("apiVersion"),
|
|
||||||
fieldpath.MakePathOrDie("kind"),
|
|
||||||
fieldpath.MakePathOrDie("metadata"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "name"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "namespace"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "selfLink"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "uid"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "clusterName"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "generation"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "managedFields"),
|
|
||||||
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// stripFields removes a predefined set of paths found in typed from managed and returns the updated ManagedFields
|
|
||||||
func (f *fieldManager) stripFields(managed fieldpath.ManagedFields, manager string) fieldpath.ManagedFields {
|
|
||||||
vs, ok := managed[manager]
|
|
||||||
if ok {
|
|
||||||
if vs == nil {
|
|
||||||
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
|
|
||||||
}
|
|
||||||
newSet := vs.Set().Difference(stripSet)
|
|
||||||
if newSet.Empty() {
|
|
||||||
delete(managed, manager)
|
|
||||||
} else {
|
|
||||||
managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return managed
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ type fakeObjectDefaulter struct{}
|
|||||||
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
|
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
|
||||||
|
|
||||||
type TestFieldManager struct {
|
type TestFieldManager struct {
|
||||||
fieldManager fieldmanager.FieldManager
|
fieldManager fieldmanager.Manager
|
||||||
emptyObj runtime.Object
|
emptyObj runtime.Object
|
||||||
liveObj runtime.Object
|
liveObj runtime.Object
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := fieldmanager.NewFieldManager(
|
f, err := fieldmanager.NewStructuredMergeManager(
|
||||||
m,
|
m,
|
||||||
&fakeObjectConvertor{},
|
&fakeObjectConvertor{},
|
||||||
&fakeObjectDefaulter{},
|
&fakeObjectDefaulter{},
|
||||||
@ -93,6 +93,8 @@ func NewTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
|
|||||||
live := &unstructured.Unstructured{}
|
live := &unstructured.Unstructured{}
|
||||||
live.SetKind(gvk.Kind)
|
live.SetKind(gvk.Kind)
|
||||||
live.SetAPIVersion(gvk.GroupVersion().String())
|
live.SetAPIVersion(gvk.GroupVersion().String())
|
||||||
|
f = fieldmanager.NewStripMetaManager(f)
|
||||||
|
f = fieldmanager.NewBuildManagerInfoManager(f, gvk.GroupVersion())
|
||||||
return TestFieldManager{
|
return TestFieldManager{
|
||||||
fieldManager: f,
|
fieldManager: f,
|
||||||
emptyObj: live,
|
emptyObj: live,
|
||||||
@ -105,7 +107,7 @@ func (f *TestFieldManager) Reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *TestFieldManager) Apply(obj []byte, manager string, force bool) error {
|
func (f *TestFieldManager) Apply(obj []byte, manager string, force bool) error {
|
||||||
out, err := f.fieldManager.Apply(f.liveObj, obj, manager, force)
|
out, err := fieldmanager.NewFieldManager(f.fieldManager).Apply(f.liveObj, obj, manager, force)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.liveObj = out
|
f.liveObj = out
|
||||||
}
|
}
|
||||||
@ -113,7 +115,7 @@ func (f *TestFieldManager) Apply(obj []byte, manager string, force bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *TestFieldManager) Update(obj runtime.Object, manager string) error {
|
func (f *TestFieldManager) Update(obj runtime.Object, manager string) error {
|
||||||
out, err := f.fieldManager.Update(f.liveObj, obj, manager)
|
out, err := fieldmanager.NewFieldManager(f.fieldManager).Update(f.liveObj, obj, manager)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.liveObj = out
|
f.liveObj = out
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,38 @@ import (
|
|||||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
// ManagedInterface groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||||
type Managed struct {
|
type ManagedInterface interface {
|
||||||
Fields fieldpath.ManagedFields
|
// Fields gets the fieldpath.ManagedFields.
|
||||||
Times map[string]*metav1.Time
|
Fields() fieldpath.ManagedFields
|
||||||
|
|
||||||
|
// Times gets the timestamps associated with each operation.
|
||||||
|
Times() map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type managedStruct struct {
|
||||||
|
fields fieldpath.ManagedFields
|
||||||
|
times map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ManagedInterface = &managedStruct{}
|
||||||
|
|
||||||
|
// Fields implements ManagedInterface.
|
||||||
|
func (m *managedStruct) Fields() fieldpath.ManagedFields {
|
||||||
|
return m.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times implements ManagedInterface.
|
||||||
|
func (m *managedStruct) Times() map[string]*metav1.Time {
|
||||||
|
return m.times
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation.
|
||||||
|
func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface {
|
||||||
|
return &managedStruct{
|
||||||
|
fields: f,
|
||||||
|
times: t,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveObjectManagedFields removes the ManagedFields from the object
|
// RemoveObjectManagedFields removes the ManagedFields from the object
|
||||||
@ -46,9 +74,9 @@ func RemoveObjectManagedFields(obj runtime.Object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
|
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
|
||||||
func DecodeObjectManagedFields(from runtime.Object) (Managed, error) {
|
func DecodeObjectManagedFields(from runtime.Object) (ManagedInterface, error) {
|
||||||
if from == nil {
|
if from == nil {
|
||||||
return Managed{}, nil
|
return &managedStruct{}, nil
|
||||||
}
|
}
|
||||||
accessor, err := meta.Accessor(from)
|
accessor, err := meta.Accessor(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,13 +85,13 @@ func DecodeObjectManagedFields(from runtime.Object) (Managed, error) {
|
|||||||
|
|
||||||
managed, err := decodeManagedFields(accessor.GetManagedFields())
|
managed, err := decodeManagedFields(accessor.GetManagedFields())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Managed{}, fmt.Errorf("failed to convert managed fields from API: %v", err)
|
return nil, fmt.Errorf("failed to convert managed fields from API: %v", err)
|
||||||
}
|
}
|
||||||
return managed, err
|
return &managed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
||||||
func EncodeObjectManagedFields(obj runtime.Object, managed Managed) error {
|
func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) error {
|
||||||
accessor, err := meta.Accessor(obj)
|
accessor, err := meta.Accessor(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
@ -80,19 +108,19 @@ func EncodeObjectManagedFields(obj runtime.Object, managed Managed) error {
|
|||||||
|
|
||||||
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
||||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||||
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managed Managed, err error) {
|
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managed managedStruct, err error) {
|
||||||
managed.Fields = make(fieldpath.ManagedFields, len(encodedManagedFields))
|
managed.fields = make(fieldpath.ManagedFields, len(encodedManagedFields))
|
||||||
managed.Times = make(map[string]*metav1.Time, len(encodedManagedFields))
|
managed.times = make(map[string]*metav1.Time, len(encodedManagedFields))
|
||||||
for _, encodedVersionedSet := range encodedManagedFields {
|
for _, encodedVersionedSet := range encodedManagedFields {
|
||||||
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Managed{}, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
return managedStruct{}, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||||
}
|
}
|
||||||
managed.Fields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
managed.fields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Managed{}, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
return managedStruct{}, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||||
}
|
}
|
||||||
managed.Times[manager] = encodedVersionedSet.Time
|
managed.times[manager] = encodedVersionedSet.Time
|
||||||
}
|
}
|
||||||
return managed, nil
|
return managed, nil
|
||||||
}
|
}
|
||||||
@ -139,15 +167,18 @@ func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (version
|
|||||||
|
|
||||||
// encodeManagedFields converts ManagedFields from the format used by
|
// encodeManagedFields converts ManagedFields from the format used by
|
||||||
// sigs.k8s.io/structured-merge-diff to the wire format (api format)
|
// sigs.k8s.io/structured-merge-diff to the wire format (api format)
|
||||||
func encodeManagedFields(managed Managed) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
func encodeManagedFields(managed ManagedInterface) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||||
|
if len(managed.Fields()) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
||||||
for manager := range managed.Fields {
|
for manager := range managed.Fields() {
|
||||||
versionedSet := managed.Fields[manager]
|
versionedSet := managed.Fields()[manager]
|
||||||
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
||||||
}
|
}
|
||||||
if t, ok := managed.Times[manager]; ok {
|
if t, ok := managed.Times()[manager]; ok {
|
||||||
v.Time = t
|
v.Time = t
|
||||||
}
|
}
|
||||||
encodedManagedFields = append(encodedManagedFields, *v)
|
encodedManagedFields = append(encodedManagedFields, *v)
|
||||||
@ -174,7 +205,10 @@ func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry)
|
|||||||
return p.Time.Before(q.Time)
|
return p.Time.Before(q.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.Manager < q.Manager
|
if p.Manager != q.Manager {
|
||||||
|
return p.Manager < q.Manager
|
||||||
|
}
|
||||||
|
return p.APIVersion < q.APIVersion
|
||||||
})
|
})
|
||||||
|
|
||||||
return encodedManagedFields, nil
|
return encodedManagedFields, nil
|
||||||
|
@ -31,6 +31,8 @@ import (
|
|||||||
// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back
|
// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back
|
||||||
func TestRoundTripManagedFields(t *testing.T) {
|
func TestRoundTripManagedFields(t *testing.T) {
|
||||||
tests := []string{
|
tests := []string{
|
||||||
|
`null
|
||||||
|
`,
|
||||||
`- apiVersion: v1
|
`- apiVersion: v1
|
||||||
fieldsType: FieldsV1
|
fieldsType: FieldsV1
|
||||||
fieldsV1:
|
fieldsV1:
|
||||||
@ -146,7 +148,7 @@ func TestRoundTripManagedFields(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("did not expect decoding error but got: %v", err)
|
t.Fatalf("did not expect decoding error but got: %v", err)
|
||||||
}
|
}
|
||||||
encoded, err := encodeManagedFields(decoded)
|
encoded, err := encodeManagedFields(&decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("did not expect encoding error but got: %v", err)
|
t.Fatalf("did not expect encoding error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -19,61 +19,48 @@ package fieldmanager
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
type skipNonAppliedManager struct {
|
type skipNonAppliedManager struct {
|
||||||
fieldManager FieldManager
|
fieldManager Manager
|
||||||
objectCreater runtime.ObjectCreater
|
objectCreater runtime.ObjectCreater
|
||||||
gvk schema.GroupVersionKind
|
gvk schema.GroupVersionKind
|
||||||
|
beforeApplyManagerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ FieldManager = &skipNonAppliedManager{}
|
var _ Manager = &skipNonAppliedManager{}
|
||||||
|
|
||||||
// NewSkipNonAppliedManager creates a new wrapped FieldManager that only starts tracking managers after the first apply
|
// NewSkipNonAppliedManager creates a new wrapped FieldManager that only starts tracking managers after the first apply.
|
||||||
func NewSkipNonAppliedManager(fieldManager FieldManager, objectCreater runtime.ObjectCreater, gvk schema.GroupVersionKind) FieldManager {
|
func NewSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater, gvk schema.GroupVersionKind) Manager {
|
||||||
return &skipNonAppliedManager{
|
return &skipNonAppliedManager{
|
||||||
fieldManager: fieldManager,
|
fieldManager: fieldManager,
|
||||||
objectCreater: objectCreater,
|
objectCreater: objectCreater,
|
||||||
gvk: gvk,
|
gvk: gvk,
|
||||||
|
beforeApplyManagerName: "before-first-apply",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update implements FieldManager.
|
// Update implements Manager.
|
||||||
func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
|
func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
liveObjAccessor, err := meta.Accessor(liveObj)
|
if len(managed.Fields()) == 0 {
|
||||||
if err != nil {
|
return newObj, managed, nil
|
||||||
return newObj, nil
|
|
||||||
}
|
}
|
||||||
newObjAccessor, err := meta.Accessor(newObj)
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
if err != nil {
|
|
||||||
return newObj, nil
|
|
||||||
}
|
|
||||||
if len(liveObjAccessor.GetManagedFields()) == 0 && len(newObjAccessor.GetManagedFields()) == 0 {
|
|
||||||
return newObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.fieldManager.Update(liveObj, newObj, manager)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply implements FieldManager.
|
// Apply implements Manager.
|
||||||
func (f *skipNonAppliedManager) Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error) {
|
func (f *skipNonAppliedManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
|
||||||
liveObjAccessor, err := meta.Accessor(liveObj)
|
if len(managed.Fields()) == 0 {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
|
||||||
}
|
|
||||||
if len(liveObjAccessor.GetManagedFields()) == 0 {
|
|
||||||
emptyObj, err := f.objectCreater.New(f.gvk)
|
emptyObj, err := f.objectCreater.New(f.gvk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create empty object of type %v: %v", f.gvk, err)
|
return nil, nil, fmt.Errorf("failed to create empty object of type %v: %v", f.gvk, err)
|
||||||
}
|
}
|
||||||
liveObj, err = f.fieldManager.Update(emptyObj, liveObj, "before-first-apply")
|
liveObj, managed, err = f.fieldManager.Update(emptyObj, liveObj, managed, f.beforeApplyManagerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create manager for existing fields: %v", err)
|
return nil, nil, fmt.Errorf("failed to create manager for existing fields: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return f.fieldManager.Apply(liveObj, patch, managed, fieldManager, force)
|
||||||
return f.fieldManager.Apply(liveObj, patch, fieldManager, force)
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stripMetaManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
|
||||||
|
// stripSet is the list of fields that should never be part of a mangedFields.
|
||||||
|
stripSet *fieldpath.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &stripMetaManager{}
|
||||||
|
|
||||||
|
// NewStripMetaManager creates a new Manager that strips metadata and typemeta fields from the manager's fieldset.
|
||||||
|
func NewStripMetaManager(fieldManager Manager) Manager {
|
||||||
|
return &stripMetaManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
stripSet: fieldpath.NewSet(
|
||||||
|
fieldpath.MakePathOrDie("apiVersion"),
|
||||||
|
fieldpath.MakePathOrDie("kind"),
|
||||||
|
fieldpath.MakePathOrDie("metadata"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "name"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "namespace"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "selfLink"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "uid"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "clusterName"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "generation"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "managedFields"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *stripMetaManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
newObj, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
f.stripFields(managed.Fields(), manager)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *stripMetaManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
newObj, managed, err := f.fieldManager.Apply(liveObj, patch, managed, manager, force)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
f.stripFields(managed.Fields(), manager)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripFields removes a predefined set of paths found in typed from managed
|
||||||
|
func (f *stripMetaManager) stripFields(managed fieldpath.ManagedFields, manager string) {
|
||||||
|
vs, ok := managed[manager]
|
||||||
|
if ok {
|
||||||
|
if vs == nil {
|
||||||
|
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
|
||||||
|
}
|
||||||
|
newSet := vs.Set().Difference(f.stripSet)
|
||||||
|
if newSet.Empty() {
|
||||||
|
delete(managed, manager)
|
||||||
|
} else {
|
||||||
|
managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"k8s.io/klog"
|
||||||
|
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/merge"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type structuredMergeManager struct {
|
||||||
|
typeConverter internal.TypeConverter
|
||||||
|
objectConverter runtime.ObjectConvertor
|
||||||
|
objectDefaulter runtime.ObjectDefaulter
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
hubVersion schema.GroupVersion
|
||||||
|
updater merge.Updater
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &structuredMergeManager{}
|
||||||
|
|
||||||
|
// NewStructuredMergeManager creates a new Manager that merges apply requests
|
||||||
|
// and update managed fields for other types of requests.
|
||||||
|
func NewStructuredMergeManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (Manager, error) {
|
||||||
|
typeConverter, err := internal.NewTypeConverter(models, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &structuredMergeManager{
|
||||||
|
typeConverter: typeConverter,
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
objectDefaulter: objectDefaulter,
|
||||||
|
groupVersion: gv,
|
||||||
|
hubVersion: hub,
|
||||||
|
updater: merge.Updater{
|
||||||
|
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCRDStructuredMergeManager creates a new Manager specifically for
|
||||||
|
// CRDs. This allows for the possibility of fields which are not defined
|
||||||
|
// in models, as well as having no models defined at all.
|
||||||
|
func NewCRDStructuredMergeManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, preserveUnknownFields bool) (_ Manager, err error) {
|
||||||
|
var typeConverter internal.TypeConverter = internal.DeducedTypeConverter{}
|
||||||
|
if models != nil {
|
||||||
|
typeConverter, err = internal.NewTypeConverter(models, preserveUnknownFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &structuredMergeManager{
|
||||||
|
typeConverter: typeConverter,
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
objectDefaulter: objectDefaulter,
|
||||||
|
groupVersion: gv,
|
||||||
|
hubVersion: hub,
|
||||||
|
updater: merge.Updater{
|
||||||
|
Converter: internal.NewCRDVersionConverter(typeConverter, objectConverter, hub),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
newObjVersioned, err := f.toVersioned(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||||
|
}
|
||||||
|
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||||
|
}
|
||||||
|
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
||||||
|
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed new object: %v", err)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
||||||
|
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed live object: %v", err)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
|
||||||
|
// TODO(apelisse) use the first return value when unions are implemented
|
||||||
|
self := "current-operation"
|
||||||
|
_, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), self)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
||||||
|
}
|
||||||
|
managed = internal.NewManaged(managedFields, managed.Times())
|
||||||
|
|
||||||
|
// If the current operation took any fields from anything, it means the object changed,
|
||||||
|
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
|
||||||
|
if vs, ok := managed.Fields()[self]; ok {
|
||||||
|
delete(managed.Fields(), self)
|
||||||
|
|
||||||
|
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||||
|
if previous, ok := managed.Fields()[manager]; ok {
|
||||||
|
managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
|
||||||
|
} else {
|
||||||
|
managed.Fields()[manager] = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *structuredMergeManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
|
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
||||||
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the patch object has the same version as the live object
|
||||||
|
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
||||||
|
return nil, nil,
|
||||||
|
errors.NewBadRequest(
|
||||||
|
fmt.Sprintf("Incorrect version specified in apply patch. "+
|
||||||
|
"Specified patch version: %s, expected: %s",
|
||||||
|
patchObj.GetAPIVersion(), f.groupVersion.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if patchObj.GetManagedFields() != nil {
|
||||||
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
|
||||||
|
}
|
||||||
|
|
||||||
|
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchObjTyped, err := f.typeConverter.ObjectToTyped(patchObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create typed patch object: %v", err)
|
||||||
|
}
|
||||||
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields(), manager, force)
|
||||||
|
if err != nil {
|
||||||
|
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||||
|
return nil, nil, internal.NewConflictError(conflicts)
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
managed = internal.NewManaged(managedFields, managed.Times())
|
||||||
|
|
||||||
|
// Update the time in the managedFieldsEntry for this operation
|
||||||
|
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||||
|
|
||||||
|
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new typed object to object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newObjVersioned, err := f.toVersioned(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||||
|
}
|
||||||
|
f.objectDefaulter.Default(newObjVersioned)
|
||||||
|
|
||||||
|
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert to unversioned: %v", err)
|
||||||
|
}
|
||||||
|
return newObjUnversioned, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *structuredMergeManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *structuredMergeManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
||||||
|
}
|
@ -296,7 +296,7 @@ type patchMechanism interface {
|
|||||||
type jsonPatcher struct {
|
type jsonPatcher struct {
|
||||||
*patcher
|
*patcher
|
||||||
|
|
||||||
fieldManager fieldmanager.FieldManager
|
fieldManager *fieldmanager.FieldManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||||
@ -382,7 +382,7 @@ type smpPatcher struct {
|
|||||||
|
|
||||||
// Schema
|
// Schema
|
||||||
schemaReferenceObj runtime.Object
|
schemaReferenceObj runtime.Object
|
||||||
fieldManager fieldmanager.FieldManager
|
fieldManager *fieldmanager.FieldManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||||
@ -422,7 +422,7 @@ type applyPatcher struct {
|
|||||||
options *metav1.PatchOptions
|
options *metav1.PatchOptions
|
||||||
creater runtime.ObjectCreater
|
creater runtime.ObjectCreater
|
||||||
kind schema.GroupVersionKind
|
kind schema.GroupVersionKind
|
||||||
fieldManager fieldmanager.FieldManager
|
fieldManager *fieldmanager.FieldManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
|
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
|
||||||
|
@ -67,7 +67,7 @@ type RequestScope struct {
|
|||||||
EquivalentResourceMapper runtime.EquivalentResourceMapper
|
EquivalentResourceMapper runtime.EquivalentResourceMapper
|
||||||
|
|
||||||
TableConvertor rest.TableConvertor
|
TableConvertor rest.TableConvertor
|
||||||
FieldManager fieldmanager.FieldManager
|
FieldManager *fieldmanager.FieldManager
|
||||||
|
|
||||||
Resource schema.GroupVersionResource
|
Resource schema.GroupVersionResource
|
||||||
Kind schema.GroupVersionKind
|
Kind schema.GroupVersionKind
|
||||||
|
@ -550,18 +550,17 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
||||||
}
|
}
|
||||||
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||||
fm, err := fieldmanager.NewFieldManager(
|
reqScope.FieldManager, err = fieldmanager.NewDefaultFieldManager(
|
||||||
a.group.OpenAPIModels,
|
a.group.OpenAPIModels,
|
||||||
a.group.UnsafeConvertor,
|
a.group.UnsafeConvertor,
|
||||||
a.group.Defaulter,
|
a.group.Defaulter,
|
||||||
fqKindToRegister.GroupVersion(),
|
a.group.Creater,
|
||||||
|
fqKindToRegister,
|
||||||
reqScope.HubGroupVersion,
|
reqScope.HubGroupVersion,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||||
}
|
}
|
||||||
fm = fieldmanager.NewSkipNonAppliedManager(fm, a.group.Creater, fqKindToRegister)
|
|
||||||
reqScope.FieldManager = fm
|
|
||||||
}
|
}
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
producedObject := storageMeta.ProducesObject(action.Verb)
|
producedObject := storageMeta.ProducesObject(action.Verb)
|
||||||
|
@ -180,7 +180,7 @@ func TestNoOpUpdateSameResourceVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
o, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
Namespace("default").
|
Namespace("default").
|
||||||
Param("fieldManager", "apply_test").
|
Param("fieldManager", "apply_test").
|
||||||
Resource(podResource).
|
Resource(podResource).
|
||||||
@ -192,23 +192,6 @@ func TestNoOpUpdateSameResourceVersion(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create object: %v", err)
|
t.Fatalf("Failed to create object: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to update once for some reason
|
|
||||||
// TODO (#82042): Remove this update once possible
|
|
||||||
b, err := json.MarshalIndent(o, "\t", "\t")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to marshal created object: %v", err)
|
|
||||||
}
|
|
||||||
_, err = client.CoreV1().RESTClient().Put().
|
|
||||||
Namespace("default").
|
|
||||||
Resource(podResource).
|
|
||||||
Name(podName).
|
|
||||||
Body(b).
|
|
||||||
Do().
|
|
||||||
Get()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to apply first no-op update: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for one second to make sure that the times of each update operation is different.
|
// Sleep for one second to make sure that the times of each update operation is different.
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
@ -386,6 +369,75 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestApplyGroupsManySeparateUpdates tests that when many different managers update the same object,
|
||||||
|
// the number of managedFields entries will only grow to a certain size.
|
||||||
|
func TestApplyGroupsManySeparateUpdates(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
|
|
||||||
|
_, client, closeFn := setup(t)
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
obj := []byte(`{
|
||||||
|
"apiVersion": "admissionregistration.k8s.io/v1",
|
||||||
|
"kind": "ValidatingWebhookConfiguration",
|
||||||
|
"metadata": {
|
||||||
|
"name": "webhook",
|
||||||
|
"labels": {"applier":"true"},
|
||||||
|
},
|
||||||
|
}`)
|
||||||
|
|
||||||
|
object, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
|
AbsPath("/apis/admissionregistration.k8s.io/v1").
|
||||||
|
Resource("validatingwebhookconfigurations").
|
||||||
|
Name("webhook").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body(obj).Do().Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
unique := fmt.Sprintf("updater%v", i)
|
||||||
|
version := "v1"
|
||||||
|
if i%2 == 0 {
|
||||||
|
version = "v1beta1"
|
||||||
|
}
|
||||||
|
object, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
|
||||||
|
AbsPath("/apis/admissionregistration.k8s.io/"+version).
|
||||||
|
Resource("validatingwebhookconfigurations").
|
||||||
|
Name("webhook").
|
||||||
|
Param("fieldManager", unique).
|
||||||
|
Body([]byte(`{"metadata":{"labels":{"` + unique + `":"new"}}}`)).Do().Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to patch object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor, err := meta.Accessor(object)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get meta accessor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect 11 entries, because the cap for update entries is 10, and 1 apply entry
|
||||||
|
if actual, expected := len(accessor.GetManagedFields()), 11; actual != expected {
|
||||||
|
if b, err := json.MarshalIndent(object, "\t", "\t"); err == nil {
|
||||||
|
t.Fatalf("Object expected to contain %v entries in managedFields, but got %v:\n%v", expected, actual, string(b))
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Object expected to contain %v entries in managedFields, but got %v: error marshalling object: %v", expected, actual, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the first entry to have the manager name "apply_test"
|
||||||
|
if actual, expected := accessor.GetManagedFields()[0].Manager, "apply_test"; actual != expected {
|
||||||
|
t.Fatalf("Expected first manager to be named %v but got %v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the second entry to have the manager name "ancient-changes"
|
||||||
|
if actual, expected := accessor.GetManagedFields()[1].Manager, "ancient-changes"; actual != expected {
|
||||||
|
t.Fatalf("Expected first manager to be named %v but got %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestApplyManagedFields makes sure that managedFields api does not change
|
// TestApplyManagedFields makes sure that managedFields api does not change
|
||||||
func TestApplyManagedFields(t *testing.T) {
|
func TestApplyManagedFields(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
@ -941,7 +993,7 @@ func TestApplyConvertsManagedFieldsVersion(t *testing.T) {
|
|||||||
Time: actual.Time,
|
Time: actual.Time,
|
||||||
FieldsType: "FieldsV1",
|
FieldsType: "FieldsV1",
|
||||||
FieldsV1: &metav1.FieldsV1{
|
FieldsV1: &metav1.FieldsV1{
|
||||||
Raw: []byte(`{"f:metadata":{"f:labels":{"f:sidecar_version":{}}},"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"sidecar\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`),
|
Raw: []byte(`{"f:metadata":{"f:labels":{"f:sidecar_version":{}}},"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"sidecar\"}":{"f:image":{},"f:name":{},".":{}}}}}}}`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user