Files
grafana/pkg/apiserver/registry/generic/strategy_test.go
2025-04-10 14:42:23 +02:00

466 lines
13 KiB
Go

package generic_test
import (
"testing"
"github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/apis/example"
)
func TestGenericStrategy(t *testing.T) {
t.Parallel()
gv := schema.GroupVersion{Group: "test", Version: "v1"}
t.Run("PrepareForUpdate", func(t *testing.T) {
t.Parallel()
obj := &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"),
},
}
t.Run("ignores status updates", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Status.Phase = example.PodPhase("Stopped")
expectedObj := obj.DeepCopy()
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("does not increment generation if annotations, labels, finalizers, or owner references change", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Annotations = map[string]string{"foo": "baz"}
newObj.Labels = map[string]string{"foo": "baz"}
newObj.Finalizers = []string{"foo"}
newObj.OwnerReferences = []metav1.OwnerReference{{Name: "foo"}}
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
assert.Equal(t, map[string]string{"foo": "baz"}, newObj.Annotations)
assert.Equal(t, map[string]string{"foo": "baz"}, newObj.Labels)
assert.Equal(t, []string{"foo"}, newObj.Finalizers)
assert.Equal(t, []metav1.OwnerReference{{Name: "foo"}}, newObj.OwnerReferences)
assert.Equal(t, int64(1), newObj.Generation)
})
t.Run("does not increment generation if spec changes", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Spec.NodeSelector = map[string]string{"foo": "baz"}
expectedObj := newObj.DeepCopy()
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
})
t.Run("PrepareForCreate", func(t *testing.T) {
t.Parallel()
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"),
},
}
t.Run("assigns generation=1", func(t *testing.T) {
t.Parallel()
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
obj := obj.DeepCopy()
strategy.PrepareForCreate(t.Context(), obj)
require.Equal(t, int64(1), obj.Generation)
})
t.Run("clears status", func(t *testing.T) {
t.Parallel()
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
obj := obj.DeepCopy()
strategy.PrepareForCreate(t.Context(), obj)
require.Equal(t, example.PodStatus{}, obj.Status)
})
t.Run("leaves spec untouched", func(t *testing.T) {
t.Parallel()
strategy := generic.NewStrategy(runtime.NewScheme(), gv)
obj := obj.DeepCopy()
originalSpec := *obj.Spec.DeepCopy()
strategy.PrepareForCreate(t.Context(), obj)
require.Equal(t, originalSpec, obj.Spec)
})
})
}
func TestStatusStrategy(t *testing.T) {
t.Parallel()
gv := schema.GroupVersion{Group: "test", Version: "v1"}
t.Run("PrepareForUpdate", func(t *testing.T) {
t.Parallel()
obj := &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"),
},
}
t.Run("ignores spec updates", func(t *testing.T) {
// The assumption here is that the status strategy should not allow for spec updates.
// This is drawn due to the GetResetFields function returning `metadata` and `spec`, and due to it copying old `metadata` fields to the new object (but not spec?).
t.Skip("assumption does not hold -- verify with app platform if this is intended")
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Spec.NodeSelector = map[string]string{"foo": "baz"}
expectedObj := obj.DeepCopy()
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("ignores label updates", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Labels = map[string]string{"foo": "baz"}
expectedObj := obj.DeepCopy()
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("ignores annotation updates", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Annotations = map[string]string{"foo": "baz"}
expectedObj := obj.DeepCopy()
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("ignores finalizer updates", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Finalizers = []string{"foo"}
expectedObj := obj.DeepCopy()
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("ignores owner references updates", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.OwnerReferences = []metav1.OwnerReference{{Name: "foo"}}
expectedObj := obj.DeepCopy()
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, expectedObj, newObj)
})
t.Run("does not increment generation on status changes", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Status.Phase = example.PodPhase("Stopped")
strategy := generic.NewStatusStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, int64(1), newObj.Generation)
})
})
}
func TestCompleteStrategy(t *testing.T) {
t.Parallel()
gv := schema.GroupVersion{Group: "test", Version: "v1"}
t.Run("PrepareForUpdate", func(t *testing.T) {
t.Parallel()
obj := &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"),
},
}
t.Run("on status updates", func(t *testing.T) {
t.Parallel()
t.Run("keeps the change", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Status.Phase = example.PodPhase("Stopped")
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, example.PodPhase("Stopped"), newObj.Status.Phase)
})
t.Run("does not change generation", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Status.Phase = example.PodPhase("Stopped")
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, int64(1), newObj.Generation)
})
})
t.Run("on spec updates", func(t *testing.T) {
t.Parallel()
t.Run("keeps the change", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Spec.NodeSelector = map[string]string{"foo": "baz"}
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, map[string]string{"foo": "baz"}, newObj.Spec.NodeSelector)
})
t.Run("does not increment generation", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Spec.NodeSelector = map[string]string{"foo": "baz"}
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
require.Equal(t, int64(1), newObj.Generation)
})
})
t.Run("on metadata updates", func(t *testing.T) {
t.Parallel()
t.Run("keeps the change", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Annotations = map[string]string{"foo": "baz"}
newObj.Labels = map[string]string{"foo": "baz"}
newObj.Finalizers = []string{"foo"}
newObj.OwnerReferences = []metav1.OwnerReference{{Name: "foo"}}
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
assert.Equal(t, map[string]string{"foo": "baz"}, newObj.Annotations)
assert.Equal(t, map[string]string{"foo": "baz"}, newObj.Labels)
assert.Equal(t, []string{"foo"}, newObj.Finalizers)
assert.Equal(t, []metav1.OwnerReference{{Name: "foo"}}, newObj.OwnerReferences)
})
t.Run("does not increment generation", func(t *testing.T) {
t.Parallel()
oldObj := obj.DeepCopy()
newObj := obj.DeepCopy()
newObj.Annotations = map[string]string{"foo": "baz"}
newObj.Labels = map[string]string{"foo": "baz"}
newObj.Finalizers = []string{"foo"}
newObj.OwnerReferences = []metav1.OwnerReference{{Name: "foo"}}
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
strategy.PrepareForUpdate(t.Context(), newObj, oldObj)
assert.Equal(t, int64(1), newObj.Generation)
})
})
})
t.Run("PrepareForCreate", func(t *testing.T) {
t.Parallel()
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"),
},
}
t.Run("assigns generation=1", func(t *testing.T) {
t.Parallel()
t.Run("when generation is not set", func(t *testing.T) {
t.Parallel()
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
obj := obj.DeepCopy()
strategy.PrepareForCreate(t.Context(), obj)
require.Equal(t, int64(1), obj.Generation)
})
t.Run("when generation is set to a higher value", func(t *testing.T) {
t.Parallel()
strategy := generic.NewCompleteStrategy(runtime.NewScheme(), gv)
obj := obj.DeepCopy()
obj.Generation = 2
strategy.PrepareForCreate(t.Context(), obj)
require.Equal(t, int64(1), obj.Generation)
})
})
})
}
func TestGetAttrs(t *testing.T) {
t.Parallel()
t.Run("returns all labels", func(t *testing.T) {
t.Parallel()
t.Run("when labels is nil", func(t *testing.T) {
t.Parallel()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
Labels: nil,
},
}
labels, _, err := generic.GetAttrs(obj)
require.NoError(t, err)
require.Empty(t, labels, "expected no labels")
})
t.Run("when there are no labels", func(t *testing.T) {
t.Parallel()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
Labels: make(map[string]string),
},
}
labels, _, err := generic.GetAttrs(obj)
require.NoError(t, err)
require.Empty(t, labels, "expected no labels")
})
t.Run("when there is only 1 label", func(t *testing.T) {
t.Parallel()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
Labels: map[string]string{"foo": "bar"},
},
}
l, _, err := generic.GetAttrs(obj)
require.NoError(t, err)
require.Equal(t, labels.Set{"foo": "bar"}, l, "expected labels to match")
})
t.Run("when there are many labels", func(t *testing.T) {
t.Parallel()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
Labels: map[string]string{"foo": "bar", "baz": "qux", "grafana": "is-cool"},
},
}
l, _, err := generic.GetAttrs(obj)
require.NoError(t, err)
require.Equal(t, labels.Set{"foo": "bar", "baz": "qux", "grafana": "is-cool"}, l, "expected labels to match")
})
})
t.Run("includes only name in fields", func(t *testing.T) {
t.Parallel()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
}
_, f, err := generic.GetAttrs(obj)
require.NoError(t, err)
require.Equal(t, fields.Set{"metadata.name": "test"}, f, "expected fields to match")
})
}