The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/apiserver/registry/generic/strategy_test.go

465 lines
13 KiB

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")
})
}