mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Increment generation in generic strategy (#97946)
--------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
62c1735a61
commit
8b0cd9dbb9
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@ -41,15 +42,29 @@ func (g *genericStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.S
|
|||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *genericStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {}
|
func (g *genericStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
|
meta, err := utils.MetaAccessor(obj)
|
||||||
func (g *genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
|
||||||
oldMeta, err := utils.MetaAccessor(old)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
meta.SetGeneration(1)
|
||||||
|
objCopy := obj.DeepCopyObject()
|
||||||
|
err = runtime.SetZeroValue(objCopy)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metaCopy, err := utils.MetaAccessor(objCopy)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status, err := metaCopy.GetStatus()
|
||||||
|
if err == nil {
|
||||||
|
_ = meta.SetStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status, err := oldMeta.GetStatus()
|
func (g *genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
|
oldMeta, err := utils.MetaAccessor(old)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -59,7 +74,27 @@ func (g *genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = newMeta.SetStatus(status)
|
// update shouldn't change the status
|
||||||
|
status, err := oldMeta.GetStatus()
|
||||||
|
if err != nil {
|
||||||
|
_ = newMeta.SetStatus(nil)
|
||||||
|
} else {
|
||||||
|
_ = newMeta.SetStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, err := newMeta.GetSpec()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSpec, err := oldMeta.GetSpec()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !apiequality.Semantic.DeepEqual(spec, oldSpec) {
|
||||||
|
newMeta.SetGeneration(oldMeta.GetGeneration() + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *genericStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (g *genericStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
|
134
pkg/apiserver/registry/generic/strategy_test.go
Normal file
134
pkg/apiserver/registry/generic/strategy_test.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package generic_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/apis/example"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrepareForUpdate(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
gv := schema.GroupVersion{Group: "test", Version: "v1"}
|
||||||
|
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
|
||||||
|
|
||||||
|
oldObj := &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Running"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
newObj *example.Pod
|
||||||
|
oldObj *example.Pod
|
||||||
|
expectedGen int64
|
||||||
|
expectedObj *example.Pod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ignore status updates",
|
||||||
|
newObj: &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Stopped"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
oldObj: oldObj.DeepCopy(),
|
||||||
|
expectedGen: 2,
|
||||||
|
expectedObj: &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Running"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "increment generation if spec changes",
|
||||||
|
newObj: &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Running"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
oldObj: oldObj.DeepCopy(),
|
||||||
|
expectedGen: 2,
|
||||||
|
expectedObj: &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: 2,
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Running"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
strategy.PrepareForUpdate(ctx, tc.newObj, tc.oldObj)
|
||||||
|
require.Equal(t, tc.expectedObj, tc.newObj)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareForCreate(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
gv := schema.GroupVersion{Group: "test", Version: "v1"}
|
||||||
|
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
|
||||||
|
|
||||||
|
obj := &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
NodeSelector: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Status: example.PodStatus{
|
||||||
|
Phase: example.PodPhase("Running"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
strategy.PrepareForCreate(ctx, obj)
|
||||||
|
require.Equal(t, int64(1), obj.Generation)
|
||||||
|
require.Equal(t, example.PodStatus{}, obj.Status)
|
||||||
|
}
|
@ -216,6 +216,9 @@ func (c *K8sResourceClient) sanitizeObject(v *unstructured.Unstructured, replace
|
|||||||
meta, ok := copy["metadata"].(map[string]any)
|
meta, ok := copy["metadata"].(map[string]any)
|
||||||
require.True(c.t, ok)
|
require.True(c.t, ok)
|
||||||
|
|
||||||
|
// remove generation
|
||||||
|
delete(meta, "generation")
|
||||||
|
|
||||||
replaceMeta = append(replaceMeta, "creationTimestamp", "resourceVersion", "uid")
|
replaceMeta = append(replaceMeta, "creationTimestamp", "resourceVersion", "uid")
|
||||||
for _, key := range replaceMeta {
|
for _, key := range replaceMeta {
|
||||||
old, ok := meta[key]
|
old, ok := meta[key]
|
||||||
@ -403,12 +406,18 @@ func DoRequest[T any](c *K8sTestHelper, params RequestParams, result *T) K8sResp
|
|||||||
// Read local JSON or YAML file into a resource
|
// Read local JSON or YAML file into a resource
|
||||||
func (c *K8sTestHelper) LoadYAMLOrJSONFile(fpath string) *unstructured.Unstructured {
|
func (c *K8sTestHelper) LoadYAMLOrJSONFile(fpath string) *unstructured.Unstructured {
|
||||||
c.t.Helper()
|
c.t.Helper()
|
||||||
|
return c.LoadYAMLOrJSON(string(c.LoadFile(fpath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read local file into a byte slice. Does not need to be a resource.
|
||||||
|
func (c *K8sTestHelper) LoadFile(fpath string) []byte {
|
||||||
|
c.t.Helper()
|
||||||
|
|
||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
raw, err := os.ReadFile(fpath)
|
raw, err := os.ReadFile(fpath)
|
||||||
require.NoError(c.t, err)
|
require.NoError(c.t, err)
|
||||||
require.NotEmpty(c.t, raw)
|
require.NotEmpty(c.t, raw)
|
||||||
return c.LoadYAMLOrJSON(string(raw))
|
return raw
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read local JSON or YAML file into a resource
|
// Read local JSON or YAML file into a resource
|
||||||
|
Loading…
Reference in New Issue
Block a user