Skip to content

Commit f39ba12

Browse files
seans3k8s-publishing-bot
authored andcommitted
Plumb stale GroupVersions through aggregated discovery
Kubernetes-commit: 363bcdd815c051e52954b1aab1cd503dfc19bff7
1 parent f538edf commit f39ba12

File tree

6 files changed

+841
-78
lines changed

6 files changed

+841
-78
lines changed

discovery/aggregated_discovery.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,43 +24,69 @@ import (
2424
"k8s.io/apimachinery/pkg/runtime/schema"
2525
)
2626

27+
// StaleGroupVersionError encasulates failed GroupVersion marked "stale"
28+
// in the returned AggregatedDiscovery format.
29+
type StaleGroupVersionError struct {
30+
gv schema.GroupVersion
31+
}
32+
33+
func (s StaleGroupVersionError) Error() string {
34+
return fmt.Sprintf("stale GroupVersion discovery: %v", s.gv)
35+
}
36+
2737
// SplitGroupsAndResources transforms "aggregated" discovery top-level structure into
2838
// the previous "unaggregated" discovery groups and resources.
29-
func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList) {
39+
func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) (
40+
*metav1.APIGroupList,
41+
map[schema.GroupVersion]*metav1.APIResourceList,
42+
map[schema.GroupVersion]error) {
3043
// Aggregated group list will contain the entirety of discovery, including
31-
// groups, versions, and resources.
44+
// groups, versions, and resources. GroupVersions marked "stale" are failed.
3245
groups := []*metav1.APIGroup{}
46+
failedGVs := map[schema.GroupVersion]error{}
3347
resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{}
3448
for _, aggGroup := range aggregatedGroups.Items {
35-
group, resources := convertAPIGroup(aggGroup)
49+
group, resources, failed := convertAPIGroup(aggGroup)
3650
groups = append(groups, group)
3751
for gv, resourceList := range resources {
3852
resourcesByGV[gv] = resourceList
3953
}
54+
for gv, err := range failed {
55+
failedGVs[gv] = err
56+
}
4057
}
4158
// Transform slice of groups to group list before returning.
4259
groupList := &metav1.APIGroupList{}
4360
groupList.Groups = make([]metav1.APIGroup, 0, len(groups))
4461
for _, group := range groups {
4562
groupList.Groups = append(groupList.Groups, *group)
4663
}
47-
return groupList, resourcesByGV
64+
return groupList, resourcesByGV, failedGVs
4865
}
4966

5067
// convertAPIGroup tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup,
5168
// also returning the map of APIResourceList for resources within GroupVersions.
52-
func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (*metav1.APIGroup, map[schema.GroupVersion]*metav1.APIResourceList) {
69+
func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (
70+
*metav1.APIGroup,
71+
map[schema.GroupVersion]*metav1.APIResourceList,
72+
map[schema.GroupVersion]error) {
5373
// Iterate through versions to convert to group and resources.
5474
group := &metav1.APIGroup{}
5575
gvResources := map[schema.GroupVersion]*metav1.APIResourceList{}
76+
failedGVs := map[schema.GroupVersion]error{}
5677
group.Name = g.ObjectMeta.Name
57-
for i, v := range g.Versions {
58-
version := metav1.GroupVersionForDiscovery{}
78+
for _, v := range g.Versions {
5979
gv := schema.GroupVersion{Group: g.Name, Version: v.Version}
80+
if v.Freshness == apidiscovery.DiscoveryFreshnessStale {
81+
failedGVs[gv] = StaleGroupVersionError{gv: gv}
82+
continue
83+
}
84+
version := metav1.GroupVersionForDiscovery{}
6085
version.GroupVersion = gv.String()
6186
version.Version = v.Version
6287
group.Versions = append(group.Versions, version)
63-
if i == 0 {
88+
// PreferredVersion is first non-stale Version
89+
if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) {
6490
group.PreferredVersion = version
6591
}
6692
resourceList := &metav1.APIResourceList{}
@@ -76,7 +102,7 @@ func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (*metav1.APIGroup, map[sc
76102
}
77103
gvResources[gv] = resourceList
78104
}
79-
return group, gvResources
105+
return group, gvResources, failedGVs
80106
}
81107

82108
// convertAPIResource tranforms a APIResourceDiscovery to an APIResource.

discovery/aggregated_discovery_test.go

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
3131
agg apidiscovery.APIGroupDiscoveryList
3232
expectedGroups metav1.APIGroupList
3333
expectedGVResources map[schema.GroupVersion]*metav1.APIResourceList
34+
expectedFailedGVs map[schema.GroupVersion]error
3435
}{
3536
{
3637
name: "Aggregated discovery: core/v1 group and pod resource",
@@ -90,6 +91,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
9091
},
9192
},
9293
},
94+
expectedFailedGVs: map[schema.GroupVersion]error{},
9395
},
9496
{
9597
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
@@ -179,6 +181,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
179181
},
180182
},
181183
},
184+
expectedFailedGVs: map[schema.GroupVersion]error{},
182185
},
183186
{
184187
name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
@@ -313,6 +316,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
313316
},
314317
},
315318
},
319+
expectedFailedGVs: map[schema.GroupVersion]error{},
316320
},
317321
{
318322
name: "Aggregated discovery: multiple groups with cluster-scoped resources",
@@ -447,6 +451,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
447451
},
448452
},
449453
},
454+
expectedFailedGVs: map[schema.GroupVersion]error{},
450455
},
451456
{
452457
name: "Aggregated discovery with single subresource",
@@ -534,6 +539,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
534539
},
535540
},
536541
},
542+
expectedFailedGVs: map[schema.GroupVersion]error{},
537543
},
538544
{
539545
name: "Aggregated discovery with multiple subresources",
@@ -633,11 +639,185 @@ func TestSplitGroupsAndResources(t *testing.T) {
633639
},
634640
},
635641
},
642+
expectedFailedGVs: map[schema.GroupVersion]error{},
643+
},
644+
{
645+
name: "Aggregated discovery: single failed GV at /api",
646+
agg: apidiscovery.APIGroupDiscoveryList{
647+
Items: []apidiscovery.APIGroupDiscovery{
648+
{
649+
Versions: []apidiscovery.APIVersionDiscovery{
650+
{
651+
Version: "v1",
652+
Resources: []apidiscovery.APIResourceDiscovery{
653+
{
654+
Resource: "pods",
655+
ResponseKind: &metav1.GroupVersionKind{
656+
Group: "",
657+
Version: "v1",
658+
Kind: "Pod",
659+
},
660+
Scope: apidiscovery.ScopeNamespace,
661+
},
662+
{
663+
Resource: "services",
664+
ResponseKind: &metav1.GroupVersionKind{
665+
Group: "",
666+
Version: "v1",
667+
Kind: "Service",
668+
},
669+
Scope: apidiscovery.ScopeNamespace,
670+
},
671+
},
672+
Freshness: apidiscovery.DiscoveryFreshnessStale,
673+
},
674+
},
675+
},
676+
},
677+
},
678+
// Single core Group/Version is stale, so no Version within Group.
679+
expectedGroups: metav1.APIGroupList{
680+
Groups: []metav1.APIGroup{{Name: ""}},
681+
},
682+
// Single core Group/Version is stale, so there are no expected resources.
683+
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{},
684+
expectedFailedGVs: map[schema.GroupVersion]error{
685+
{Group: "", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "", Version: "v1"}},
686+
},
687+
},
688+
{
689+
name: "Aggregated discovery: single failed GV at /apis",
690+
agg: apidiscovery.APIGroupDiscoveryList{
691+
Items: []apidiscovery.APIGroupDiscovery{
692+
{
693+
ObjectMeta: metav1.ObjectMeta{
694+
Name: "apps",
695+
},
696+
Versions: []apidiscovery.APIVersionDiscovery{
697+
{
698+
Version: "v1",
699+
Resources: []apidiscovery.APIResourceDiscovery{
700+
{
701+
Resource: "deployments",
702+
ResponseKind: &metav1.GroupVersionKind{
703+
Group: "apps",
704+
Version: "v1",
705+
Kind: "Deployment",
706+
},
707+
Scope: apidiscovery.ScopeNamespace,
708+
},
709+
{
710+
Resource: "statefulsets",
711+
ResponseKind: &metav1.GroupVersionKind{
712+
Group: "apps",
713+
Version: "v1",
714+
Kind: "StatefulSets",
715+
},
716+
Scope: apidiscovery.ScopeNamespace,
717+
},
718+
},
719+
Freshness: apidiscovery.DiscoveryFreshnessStale,
720+
},
721+
},
722+
},
723+
},
724+
},
725+
// Single apps/v1 Group/Version is stale, so no Version within Group.
726+
expectedGroups: metav1.APIGroupList{
727+
Groups: []metav1.APIGroup{{Name: "apps"}},
728+
},
729+
// Single apps/v1 Group/Version is stale, so there are no expected resources.
730+
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{},
731+
expectedFailedGVs: map[schema.GroupVersion]error{
732+
{Group: "apps", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v1"}},
733+
},
734+
},
735+
{
736+
name: "Aggregated discovery: 1 group/2 versions/1 failed GV at /apis",
737+
agg: apidiscovery.APIGroupDiscoveryList{
738+
Items: []apidiscovery.APIGroupDiscovery{
739+
{
740+
ObjectMeta: metav1.ObjectMeta{
741+
Name: "apps",
742+
},
743+
Versions: []apidiscovery.APIVersionDiscovery{
744+
// Stale v2 should report failed GV.
745+
{
746+
Version: "v2",
747+
Resources: []apidiscovery.APIResourceDiscovery{
748+
{
749+
Resource: "daemonsets",
750+
ResponseKind: &metav1.GroupVersionKind{
751+
Group: "apps",
752+
Version: "v2",
753+
Kind: "DaemonSets",
754+
},
755+
Scope: apidiscovery.ScopeNamespace,
756+
},
757+
},
758+
Freshness: apidiscovery.DiscoveryFreshnessStale,
759+
},
760+
{
761+
Version: "v1",
762+
Resources: []apidiscovery.APIResourceDiscovery{
763+
{
764+
Resource: "deployments",
765+
ResponseKind: &metav1.GroupVersionKind{
766+
Group: "apps",
767+
Version: "v1",
768+
Kind: "Deployment",
769+
},
770+
Scope: apidiscovery.ScopeNamespace,
771+
},
772+
},
773+
},
774+
},
775+
},
776+
},
777+
},
778+
// Only apps/v1 is non-stale expected Group/Version
779+
expectedGroups: metav1.APIGroupList{
780+
Groups: []metav1.APIGroup{
781+
{
782+
Name: "apps",
783+
Versions: []metav1.GroupVersionForDiscovery{
784+
{
785+
GroupVersion: "apps/v1",
786+
Version: "v1",
787+
},
788+
},
789+
// PreferredVersion must be apps/v1
790+
PreferredVersion: metav1.GroupVersionForDiscovery{
791+
GroupVersion: "apps/v1",
792+
Version: "v1",
793+
},
794+
},
795+
},
796+
},
797+
// Only apps/v1 resources expected.
798+
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{
799+
{Group: "apps", Version: "v1"}: {
800+
GroupVersion: "apps/v1",
801+
APIResources: []metav1.APIResource{
802+
{
803+
Name: "deployments",
804+
Namespaced: true,
805+
Group: "apps",
806+
Version: "v1",
807+
Kind: "Deployment",
808+
},
809+
},
810+
},
811+
},
812+
expectedFailedGVs: map[schema.GroupVersion]error{
813+
{Group: "apps", Version: "v2"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v2"}},
814+
},
636815
},
637816
}
638817

639818
for _, test := range tests {
640-
apiGroups, resourcesByGV := SplitGroupsAndResources(test.agg)
819+
apiGroups, resourcesByGV, failedGVs := SplitGroupsAndResources(test.agg)
820+
assert.Equal(t, test.expectedFailedGVs, failedGVs)
641821
assert.Equal(t, test.expectedGroups, *apiGroups)
642822
assert.Equal(t, test.expectedGVResources, resourcesByGV)
643823
}

discovery/cached/memory/memcache.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,38 @@ func (d *memCacheClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*meta
136136
// GroupsAndMaybeResources returns the list of APIGroups, and possibly the map of group/version
137137
// to resources. The returned groups will never be nil, but the resources map can be nil
138138
// if there are no cached resources.
139-
func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {
139+
func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error) {
140140
d.lock.Lock()
141141
defer d.lock.Unlock()
142142

143143
if !d.cacheValid {
144144
if err := d.refreshLocked(); err != nil {
145-
return nil, nil, err
145+
return nil, nil, nil, err
146146
}
147147
}
148148
// Build the resourceList from the cache?
149149
var resourcesMap map[schema.GroupVersion]*metav1.APIResourceList
150+
var failedGVs map[schema.GroupVersion]error
150151
if d.receivedAggregatedDiscovery && len(d.groupToServerResources) > 0 {
151152
resourcesMap = map[schema.GroupVersion]*metav1.APIResourceList{}
153+
failedGVs = map[schema.GroupVersion]error{}
152154
for gv, cacheEntry := range d.groupToServerResources {
153155
groupVersion, err := schema.ParseGroupVersion(gv)
154156
if err != nil {
155-
return nil, nil, fmt.Errorf("failed to parse group version (%v): %v", gv, err)
157+
return nil, nil, nil, fmt.Errorf("failed to parse group version (%v): %v", gv, err)
158+
}
159+
if cacheEntry.err != nil {
160+
failedGVs[groupVersion] = cacheEntry.err
161+
} else {
162+
resourcesMap[groupVersion] = cacheEntry.resourceList
156163
}
157-
resourcesMap[groupVersion] = cacheEntry.resourceList
158164
}
159165
}
160-
return d.groupList, resourcesMap, nil
166+
return d.groupList, resourcesMap, failedGVs, nil
161167
}
162168

163169
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
164-
groups, _, err := d.GroupsAndMaybeResources()
170+
groups, _, _, err := d.GroupsAndMaybeResources()
165171
if err != nil {
166172
return nil, err
167173
}
@@ -235,14 +241,19 @@ func (d *memCacheClient) refreshLocked() error {
235241

236242
if ad, ok := d.delegate.(discovery.AggregatedDiscoveryInterface); ok {
237243
var resources map[schema.GroupVersion]*metav1.APIResourceList
238-
gl, resources, err = ad.GroupsAndMaybeResources()
244+
var failedGVs map[schema.GroupVersion]error
245+
gl, resources, failedGVs, err = ad.GroupsAndMaybeResources()
239246
if resources != nil && err == nil {
240247
// Cache the resources.
241248
d.groupToServerResources = map[string]*cacheEntry{}
242249
d.groupList = gl
243250
for gv, resources := range resources {
244251
d.groupToServerResources[gv.String()] = &cacheEntry{resources, nil}
245252
}
253+
// Cache GroupVersion discovery errors
254+
for gv, err := range failedGVs {
255+
d.groupToServerResources[gv.String()] = &cacheEntry{nil, err}
256+
}
246257
d.receivedAggregatedDiscovery = true
247258
d.cacheValid = true
248259
return nil

0 commit comments

Comments
 (0)