pkg/k8score/discovery_catalog_test.go
Rama main · réplica de lectura del repositorio original.
package k8score
import (
"errors"
"slices"
"sync/atomic"
"testing"
"github.com/skyhook-io/radar/pkg/resourceid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
// scriptedDiscovery answers discovery with whatever the test sets.
type scriptedDiscovery struct {
discovery.DiscoveryInterface
lists []*metav1.APIResourceList
err error
calls atomic.Int32
}
func (d *scriptedDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
d.calls.Add(1)
return nil, d.lists, d.err
}
func widgetLists() []*metav1.APIResourceList {
verbs := []string{"delete", "get", "list", "watch"}
return []*metav1.APIResourceList{
{GroupVersion: "example.io/v1", APIResources: []metav1.APIResource{{Name: "widgets", Kind: "Widget", Namespaced: true, Verbs: verbs}}},
{GroupVersion: "example.io/v1beta1", APIResources: []metav1.APIResource{
{Name: "widgets", Kind: "Widget", Namespaced: true, Verbs: verbs},
{Name: "widgets/status", Kind: "Widget", Namespaced: true, Verbs: []string{"get"}},
}},
}
}
func TestCatalogKeepsEveryServedVersionWithoutRefreshing(t *testing.T) {
client := &scriptedDiscovery{lists: widgetLists()}
d, err := NewResourceDiscovery(client)
if err != nil {
t.Fatal(err)
}
c := d.Catalog()
var versions []string
for _, r := range c.Resources {
versions = append(versions, r.Group+"/"+r.Version+"/"+r.Name)
}
if want := []string{"example.io/v1/widgets", "example.io/v1beta1/widgets"}; !slices.Equal(versions, want) {
t.Fatalf("catalog resources = %v, want %v (every served version, no subresources)", versions, want)
}
if !c.Complete {
t.Fatal("catalog of a clean discovery must be complete")
}
c.Resources[0].Kind = "Changed"
if d.Catalog().Resources[0].Kind != "Widget" {
t.Fatal("a caller's change to its catalog reached discovery")
}
if client.calls.Load() != 1 {
t.Fatalf("discovery was called %d times; Catalog must not refresh", client.calls.Load())
}
}
func TestCatalogIsCompleteOnlyAfterACleanDiscovery(t *testing.T) {
client := &scriptedDiscovery{err: errors.New("discovery unavailable")}
d, err := NewResourceDiscovery(client)
if err != nil {
t.Fatal(err)
}
if c := d.Catalog(); len(c.Resources) != 0 || c.Complete {
t.Fatalf("discovery that never answered: %d resources, complete %v; want 0, incomplete", len(c.Resources), c.Complete)
}
// Resources added without a discovery answer change the catalog but say
// nothing about what else is served.
d.AddAPIResource(APIResource{Group: "example.io", Version: "v1", Kind: "Widget", Name: "widgets", Namespaced: true})
if c := d.Catalog(); len(c.Resources) != 1 || c.Complete {
t.Fatalf("after adding a resource to unanswered discovery: %d resources, complete %v; want 1, incomplete", len(c.Resources), c.Complete)
}
client.lists, client.err = widgetLists(), nil
if err := d.Refresh(); err != nil {
t.Fatal(err)
}
if !d.Catalog().Complete {
t.Fatal("after a refresh that returned resources: incomplete, want complete")
}
client.lists, client.err = nil, errors.New("discovery unavailable")
if err := d.Refresh(); err == nil {
t.Fatal("a refresh returning nothing must fail")
}
if c := d.Catalog(); len(c.Resources) != 2 || !c.Complete {
t.Fatalf("a failed refresh changed the catalog: %d resources, complete %v", len(c.Resources), c.Complete)
}
client.lists = widgetLists()
client.err = &discovery.ErrGroupDiscoveryFailed{Groups: map[schema.GroupVersion]error{
{Group: "metrics.k8s.io", Version: "v1beta1"}: errors.New("service unavailable"),
}}
_ = d.Refresh()
if d.Catalog().Complete {
t.Fatal("partial refresh: complete, want incomplete")
}
// Any error alongside resources leaves the answer incomplete, whatever its
// type.
client.err = errors.New("discovery interrupted")
_ = d.Refresh()
if d.Catalog().Complete {
t.Fatal("refresh with an error and resources: complete, want incomplete")
}
client.err = nil
_ = d.Refresh()
if !d.Catalog().Complete {
t.Fatal("a clean refresh must make the catalog complete again")
}
}
func TestCurrentKindResolutionDoesNotInferCertaintyAfterDiscoveryFailure(t *testing.T) {
client := &scriptedDiscovery{lists: widgetLists()}
d, err := NewResourceDiscovery(client)
if err != nil {
t.Fatal(err)
}
ref := resourceid.UnqualifiedReference("Widget", "default", "example")
if got := resourceid.ResolveCurrent(ref, d); !got.OK() || got.Uncertain {
t.Fatalf("clean discovery: %+v", got)
}
client.lists, client.err = nil, errors.New("discovery unavailable")
if err := d.Refresh(); err == nil {
t.Fatal("expected discovery failure")
}
if got := resourceid.ResolveCurrent(ref, d); !got.OK() || !got.Uncertain {
t.Fatalf("stale discovery must not establish current uniqueness: %+v", got)
}
client.lists = widgetLists()
client.err = errors.New("discovery interrupted")
_ = d.Refresh()
if got := resourceid.ResolveCurrent(ref, d); !got.OK() || !got.Uncertain {
t.Fatalf("resources with an error must not establish uniqueness: %+v", got)
}
client.err = nil
if err := d.Refresh(); err != nil {
t.Fatal(err)
}
if got := resourceid.ResolveCurrent(ref, d); !got.OK() || got.Uncertain {
t.Fatalf("recovered discovery: %+v", got)
}
}