1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-23 12:29:09 +00:00

Stop single caches instead of all of them (#812)

* Revert OnSchemas change work

* Track schema changes

* Only stop a single GVK informer factory

* Add tests

* Rename crd to crdClient

* Rename s to sqlStore

* Don't wait for synced caches if request is canceled

* Move schematracker to pkg/sqlcache/schematracker
This commit is contained in:
Tom Lebreux
2025-09-10 17:04:25 -04:00
committed by GitHub
parent 45a3b07816
commit 13d5ad3ccb
10 changed files with 543 additions and 246 deletions

View File

@@ -0,0 +1,78 @@
package schematracker
import (
"errors"
"slices"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/resources/common"
"github.com/rancher/steve/pkg/schema"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
)
type Resetter interface {
Reset(k8sschema.GroupVersionKind) error
}
type SchemaTracker struct {
knownSchemas map[k8sschema.GroupVersionKind][]common.ColumnDefinition
resetter Resetter
}
func NewSchemaTracker(resetter Resetter) *SchemaTracker {
return &SchemaTracker{
knownSchemas: make(map[k8sschema.GroupVersionKind][]common.ColumnDefinition),
resetter: resetter,
}
}
func (s *SchemaTracker) OnSchemas(schemas *schema.Collection) error {
knownSchemas := make(map[k8sschema.GroupVersionKind][]common.ColumnDefinition)
needsReset := make(map[k8sschema.GroupVersionKind]struct{})
deletedSchemas := make(map[k8sschema.GroupVersionKind]struct{})
for gvk := range s.knownSchemas {
deletedSchemas[gvk] = struct{}{}
}
for _, id := range schemas.IDs() {
theSchema := schemas.Schema(id)
if theSchema == nil {
continue
}
gvk := attributes.GVK(theSchema)
cols := common.GetColumnDefinitions(theSchema)
knownSchemas[gvk] = cols
oldCols, exists := s.knownSchemas[gvk]
if exists {
if !slices.Equal(cols, oldCols) {
needsReset[gvk] = struct{}{}
}
} else {
needsReset[gvk] = struct{}{}
}
// Schema is still there so it hasn't been deleted
delete(deletedSchemas, gvk)
}
// All deleted schemas must be resetted as well
for gvk := range deletedSchemas {
needsReset[gvk] = struct{}{}
}
// Reset known schemas
var retErr error
for gvk := range needsReset {
err := s.resetter.Reset(gvk)
retErr = errors.Join(retErr, err)
}
s.knownSchemas = knownSchemas
return retErr
}

View File

@@ -0,0 +1,231 @@
package schematracker
import (
"context"
"testing"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/resources/common"
"github.com/rancher/steve/pkg/schema"
"github.com/rancher/wrangler/v3/pkg/schemas"
"github.com/stretchr/testify/assert"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
)
type testResetter struct {
Resets map[k8sschema.GroupVersionKind]struct{}
}
func (r *testResetter) Reset(gvk k8sschema.GroupVersionKind) error {
if r.Resets == nil {
r.Resets = make(map[k8sschema.GroupVersionKind]struct{})
}
r.Resets[gvk] = struct{}{}
return nil
}
func TestSchemaTracker(t *testing.T) {
pods := &types.APISchema{
Schema: &schemas.Schema{ID: "pods"},
}
attributes.SetGVK(pods, k8sschema.GroupVersionKind{
Version: "v1",
Kind: "Pod",
})
attributes.SetGVR(pods, k8sschema.GroupVersionResource{
Version: "v1",
Resource: "pods",
})
configmaps := &types.APISchema{
Schema: &schemas.Schema{ID: "configmaps"},
}
attributes.SetGVK(configmaps, k8sschema.GroupVersionKind{
Version: "v1",
Kind: "ConfigMap",
})
attributes.SetGVR(configmaps, k8sschema.GroupVersionResource{
Version: "v1",
Resource: "configmaps",
})
foo := &types.APISchema{
Schema: &schemas.Schema{ID: "test.io.foos"},
}
attributes.SetGVK(foo, k8sschema.GroupVersionKind{
Group: "test.io",
Version: "v1",
Kind: "Foo",
})
attributes.SetGVR(foo, k8sschema.GroupVersionResource{
Group: "test.io",
Version: "v1",
Resource: "foos",
})
foos1 := &types.APISchema{
Schema: &schemas.Schema{ID: "test.io.foos"},
}
attributes.SetGVK(foos1, k8sschema.GroupVersionKind{
Group: "test.io",
Version: "v1",
Kind: "Foo",
})
attributes.SetGVR(foos1, k8sschema.GroupVersionResource{
Group: "test.io",
Version: "v1",
Resource: "foos",
})
attributes.SetColumns(foos1, []common.ColumnDefinition{
{Field: "field1"}, {Field: "field2"},
})
foos2 := &types.APISchema{
Schema: &schemas.Schema{ID: "test.io.foos"},
}
attributes.SetGVK(foos2, k8sschema.GroupVersionKind{
Group: "test.io",
Version: "v1",
Kind: "Foo",
})
attributes.SetGVR(foos2, k8sschema.GroupVersionResource{
Group: "test.io",
Version: "v1",
Resource: "foos",
})
attributes.SetColumns(foos2, []common.ColumnDefinition{
{Field: "field1"}, {Field: "field2"}, {Field: "field3"},
})
bars := &types.APISchema{
Schema: &schemas.Schema{ID: "test.io.bars"},
}
attributes.SetGVK(bars, k8sschema.GroupVersionKind{
Group: "test.io",
Version: "v1",
Kind: "Bar",
})
attributes.SetGVR(bars, k8sschema.GroupVersionResource{
Group: "test.io",
Version: "v1",
Resource: "bars",
})
tests := []struct {
name string
initialSchemas map[string]*types.APISchema
refreshedSchemas map[string]*types.APISchema
expectedResets map[k8sschema.GroupVersionKind]struct{}
}{
{
name: "no change",
initialSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
},
refreshedSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
},
},
{
name: "single schema added",
initialSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
},
refreshedSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"pods": pods,
},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(pods): {},
},
},
{
name: "multiple schemas added",
initialSchemas: map[string]*types.APISchema{},
refreshedSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"pods": pods,
},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(configmaps): {},
attributes.GVK(pods): {},
},
},
{
name: "single schema removed",
initialSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"pods": pods,
},
refreshedSchemas: map[string]*types.APISchema{
"pods": pods,
},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(configmaps): {},
},
},
{
name: "multiple schemas removed",
initialSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"pods": pods,
},
refreshedSchemas: map[string]*types.APISchema{},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(configmaps): {},
attributes.GVK(pods): {},
},
},
{
name: "field changed",
initialSchemas: map[string]*types.APISchema{
"test.io.foos": foos1,
},
refreshedSchemas: map[string]*types.APISchema{
"test.io.foos": foos2,
},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(foos2): {},
},
},
{
name: "added deleted and changed",
initialSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"pods": pods,
"test.io.foos": foos1,
},
refreshedSchemas: map[string]*types.APISchema{
"configmaps": configmaps,
"test.io.bars": bars,
"test.io.foos": foos2,
},
expectedResets: map[k8sschema.GroupVersionKind]struct{}{
attributes.GVK(foos2): {},
attributes.GVK(pods): {},
attributes.GVK(bars): {},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resetter := &testResetter{}
tracker := NewSchemaTracker(resetter)
collection := schema.NewCollection(context.TODO(), types.EmptyAPISchemas(), nil)
collection.Reset(test.initialSchemas)
err := tracker.OnSchemas(collection)
assert.NoError(t, err)
// Reset because we don't care about the initial list of resets
resetter.Resets = nil
collection.Reset(test.refreshedSchemas)
err = tracker.OnSchemas(collection)
assert.NoError(t, err)
assert.Equal(t, test.expectedResets, resetter.Resets)
})
}
}