Track ownership of deployments scale subresource
Kubernetes-commit: a9ea98b3b9272a7f7788a0d37891e4b13b9be38d
This commit is contained in:
parent
c883d6c994
commit
8df8282eaf
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Copyright 2021 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/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
var (
|
||||
scaleGroupVersion = schema.GroupVersion{Group: "autoscaling", Version: "v1"}
|
||||
replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas")
|
||||
)
|
||||
|
||||
// ResourcePathMappings maps a group/version to its replicas path. The
|
||||
// assumption is that all the paths correspond to leaf fields.
|
||||
type ResourcePathMappings map[string]fieldpath.Path
|
||||
|
||||
// ScaleHandler manages the conversion of managed fields between a main
|
||||
// resource and the scale subresource
|
||||
type ScaleHandler struct {
|
||||
parentEntries []metav1.ManagedFieldsEntry
|
||||
defaultGroupVersion schema.GroupVersion
|
||||
mappings ResourcePathMappings
|
||||
}
|
||||
|
||||
// NewScaleHandler creates a new ScaleHandler
|
||||
func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, defaultGroupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler {
|
||||
return &ScaleHandler{
|
||||
parentEntries: parentEntries,
|
||||
defaultGroupVersion: defaultGroupVersion,
|
||||
mappings: mappings,
|
||||
}
|
||||
}
|
||||
|
||||
// ToSubresource filter the managed fields of the main resource and convert
|
||||
// them so that they can be handled by scale.
|
||||
// For the managed fields that have a replicas path it performs two changes:
|
||||
// 1. APIVersion is changed to the APIVersion of the scale subresource
|
||||
// 2. Replicas path of the main resource is transformed to the replicas path of
|
||||
// the scale subresource
|
||||
func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) {
|
||||
managed, err := DecodeManagedFields(h.parentEntries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := fieldpath.ManagedFields{}
|
||||
t := map[string]*metav1.Time{}
|
||||
for manager, versionedSet := range managed.Fields() {
|
||||
path := h.mappings[string(versionedSet.APIVersion())]
|
||||
if versionedSet.Set().Has(path) {
|
||||
newVersionedSet := fieldpath.NewVersionedSet(
|
||||
fieldpath.NewSet(replicasPathInScale),
|
||||
fieldpath.APIVersion(scaleGroupVersion.String()),
|
||||
versionedSet.Applied(),
|
||||
)
|
||||
|
||||
f[manager] = newVersionedSet
|
||||
t[manager] = managed.Times()[manager]
|
||||
}
|
||||
}
|
||||
|
||||
return managedFieldsEntries(internal.NewManaged(f, t))
|
||||
}
|
||||
|
||||
// ToParent merges `scaleEntries` with the entries of the main resource and
|
||||
// transforms them accordingly
|
||||
func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) {
|
||||
decodedParentEntries, err := DecodeManagedFields(h.parentEntries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentFields := decodedParentEntries.Fields()
|
||||
|
||||
decodedScaleEntries, err := DecodeManagedFields(scaleEntries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scaleFields := decodedScaleEntries.Fields()
|
||||
|
||||
f := fieldpath.ManagedFields{}
|
||||
t := map[string]*metav1.Time{}
|
||||
|
||||
for manager, versionedSet := range parentFields {
|
||||
// Get the main resource "replicas" path
|
||||
path := h.mappings[string(versionedSet.APIVersion())]
|
||||
|
||||
// If the parent entry does not have the replicas path, just keep it as it is
|
||||
if !versionedSet.Set().Has(path) {
|
||||
f[manager] = versionedSet
|
||||
t[manager] = decodedParentEntries.Times()[manager]
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := scaleFields[manager]; !ok {
|
||||
// "Steal" the replicas path from the main resource entry
|
||||
newSet := versionedSet.Set().Difference(fieldpath.NewSet(path))
|
||||
|
||||
if !newSet.Empty() {
|
||||
newVersionedSet := fieldpath.NewVersionedSet(
|
||||
newSet,
|
||||
versionedSet.APIVersion(),
|
||||
versionedSet.Applied(),
|
||||
)
|
||||
f[manager] = newVersionedSet
|
||||
t[manager] = decodedParentEntries.Times()[manager]
|
||||
}
|
||||
} else {
|
||||
// Field wasn't stolen, let's keep the entry as it is.
|
||||
f[manager] = versionedSet
|
||||
t[manager] = decodedParentEntries.Times()[manager]
|
||||
delete(scaleFields, manager)
|
||||
}
|
||||
}
|
||||
|
||||
for manager, versionedSet := range scaleFields {
|
||||
newVersionedSet := fieldpath.NewVersionedSet(
|
||||
fieldpath.NewSet(h.mappings[h.defaultGroupVersion.String()]),
|
||||
fieldpath.APIVersion(h.defaultGroupVersion.String()),
|
||||
versionedSet.Applied(),
|
||||
)
|
||||
f[manager] = newVersionedSet
|
||||
t[manager] = decodedParentEntries.Times()[manager]
|
||||
}
|
||||
|
||||
return managedFieldsEntries(internal.NewManaged(f, t))
|
||||
}
|
||||
|
||||
func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) {
|
||||
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
if err := internal.EncodeObjectManagedFields(obj, entries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
return accessor.GetManagedFields(), nil
|
||||
}
|
|
@ -0,0 +1,684 @@
|
|||
/*
|
||||
Copyright 2021 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
func TestTransformManagedFieldsToSubresource(t *testing.T) {
|
||||
testTime, _ := time.ParseInLocation("2006-Jan-02", "2013-Feb-03", time.Local)
|
||||
managedFieldTime := v1.NewTime(testTime)
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
input []metav1.ManagedFieldsEntry
|
||||
expected []metav1.ManagedFieldsEntry
|
||||
}{
|
||||
{
|
||||
desc: "filter one entry and transform it into a subresource entry",
|
||||
input: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "manager-1",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:another-field":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "manager-2",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Time: &managedFieldTime,
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "manager-2",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Time: &managedFieldTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "transform all entries",
|
||||
input: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "manager-1",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "manager-2",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "manager-3",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "manager-1",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "manager-2",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "manager-3",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
handler := NewScaleHandler(
|
||||
test.input,
|
||||
schema.GroupVersion{Group: "apps", Version: "v1"},
|
||||
defaultMappings(),
|
||||
)
|
||||
subresourceEntries, err := handler.ToSubresource()
|
||||
if err != nil {
|
||||
t.Fatalf("test %q - expected no error but got %v", test.desc, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(subresourceEntries, test.expected) {
|
||||
t.Fatalf("test %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, subresourceEntries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformingManagedFieldsToParent(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
parent []metav1.ManagedFieldsEntry
|
||||
subresource []metav1.ManagedFieldsEntry
|
||||
expected []metav1.ManagedFieldsEntry
|
||||
}{
|
||||
{
|
||||
desc: "different-managers: apply -> update",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "different-managers: apply -> apply",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "different-managers: update -> update",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "different-managers: update -> apply",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "same manager: apply -> apply",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "same manager: update -> update",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "same manager: update -> apply",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "same manager: apply -> update",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "subresource doesn't own the path anymore",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:another":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Subresource steals all the fields of the parent resource",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "apply without stealing",
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
handler := NewScaleHandler(
|
||||
test.parent,
|
||||
schema.GroupVersion{Group: "apps", Version: "v1"},
|
||||
defaultMappings(),
|
||||
)
|
||||
parentEntries, err := handler.ToParent(test.subresource)
|
||||
if err != nil {
|
||||
t.Fatalf("test: %q - expected no error but got %v", test.desc, err)
|
||||
}
|
||||
if !reflect.DeepEqual(parentEntries, test.expected) {
|
||||
t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
mappings ResourcePathMappings
|
||||
parent []metav1.ManagedFieldsEntry
|
||||
subresource []metav1.ManagedFieldsEntry
|
||||
expected []metav1.ManagedFieldsEntry
|
||||
}{
|
||||
{
|
||||
desc: "multi-version",
|
||||
mappings: ResourcePathMappings{
|
||||
"apps/v1": fieldpath.MakePathOrDie("spec", "the-replicas"),
|
||||
"apps/v2": fieldpath.MakePathOrDie("spec", "not-the-replicas"),
|
||||
},
|
||||
parent: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test-other",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v2",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:not-the-replicas":{},"f:selector":{}}}`)},
|
||||
},
|
||||
},
|
||||
subresource: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "autoscaling/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
expected: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "test",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "test-other",
|
||||
Operation: metav1.ManagedFieldsOperationApply,
|
||||
APIVersion: "apps/v2",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)},
|
||||
},
|
||||
{
|
||||
Manager: "scale",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: "apps/v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{}}}`)},
|
||||
Subresource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
handler := NewScaleHandler(
|
||||
test.parent,
|
||||
schema.GroupVersion{Group: "apps", Version: "v1"},
|
||||
test.mappings,
|
||||
)
|
||||
parentEntries, err := handler.ToParent(test.subresource)
|
||||
if err != nil {
|
||||
t.Fatalf("test: %q - expected no error but got %v", test.desc, err)
|
||||
}
|
||||
if !reflect.DeepEqual(parentEntries, test.expected) {
|
||||
t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func defaultMappings() ResourcePathMappings {
|
||||
return ResourcePathMappings{
|
||||
"apps/v1": fieldpath.MakePathOrDie("spec", "replicas"),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue