Change:
Split off from , the thing that actually caused the flakes throughout the system (as opposed to just failing the TPR test) is (https://k8s-) Which I guess means that the discovery client got a list, but then the TPR was gone when it tried to get details? This is not an emergency since we've marked the test flaky, but we should fix it.
The print out of the error message included the message, which was generic 404. So I believe returned a 404 without a JSON body.
Yes. The discovery client returns a partial result when this error occurs, see . I think this behavior is correct. Caller should look into the error and ignore it or retry the discovery.

Discussion:
// serverPreferredResources returns the supported resources with the version preferred by the // server. If namespaced is true, only namespaced resources will be returned. func (d *DiscoveryClient) serverPreferredResources(namespaced bool) ([]unversioned.GroupVersionResource, error) { <del> results := []unversioned.GroupVersionResource{} serverGroupList, err := d.ServerGroups() if err != nil { return results, err } </del> <ins> // retry in case the groups supported by the server change after ServerGroup() returns. const maxRetries = 2 </ins> var failedGroups map[unversioned.GroupVersion]error <del> for _, apiGroup := range serverGroupList.Groups { preferredVersion := apiGroup.PreferredVersion groupVersion := unversioned.GroupVersion{Group: apiGroup.Name, Version: preferredVersion.Version} apiResourceList, err := d.ServerResourcesForGroupVersion(preferredVersion.GroupVersion) </del> <ins> var results []unversioned.GroupVersionResource RetrieveGroups: for i := 0; i < maxRetries; i++ { results = []unversioned.GroupVersionResource{} failedGroups = make(map[unversioned.GroupVersion]error) serverGroupList, err := d.ServerGroups() </ins> if err != nil { <del> if failedGroups == nil { failedGroups = make(map[unversioned.GroupVersion]error) } failedGroups[groupVersion] = err continue </del> <ins> return results, err </ins> } <del> for _, apiResource := range apiResourceList.APIResources { // ignore the root scoped resources if "namespaced" is true. if namespaced && !apiResource.Namespaced { </del> <ins> for _, apiGroup := range serverGroupList.Groups { preferredVersion := apiGroup.PreferredVersion groupVersion := unversioned.GroupVersion{Group: apiGroup.Name, Version: preferredVersion.Version} apiResourceList, err := d.ServerResourcesForGroupVersion(preferredVersion.GroupVersion) if err != nil { if i < maxRetries-1 { continue RetrieveGroups } failedGroups[groupVersion] = err </ins> continue } <del> if strings.Contains(apiResource.Name, "/") { continue </del> <ins> for _, apiResource := range apiResourceList.APIResources { // ignore the root scoped resources if "namespaced" is true. if namespaced && !apiResource.Namespaced { continue } if strings.Contains(apiResource.Name, "/") { continue } results = append(results, groupVersion.WithResource(apiResource.Name)) </ins> } <del> results = append(results, groupVersion.WithResource(apiResource.Name)) </del> <ins> } if len(failedGroups) == 0 { return results, nil </ins> } } <del> if len(failedGroups) > 0 { return results, &ErrGroupDiscoveryFailed{Groups: failedGroups} } return results, nil </del> <ins> return results, &ErrGroupDiscoveryFailed{Groups: failedGroups} </ins> } // ServerPreferredResources returns the supported resources with the version preferred by the

server.Close() } } <ins> func TestGetServerPreferredResourcesRetries(t *testing.T) { stable := unversioned.APIResourceList{ GroupVersion: "v1", APIResources: []unversioned.APIResource{ {Name: "pods", Namespaced: true, Kind: "Pod"}, }, } beta := unversioned.APIResourceList{ GroupVersion: "extensions/v1", APIResources: []unversioned.APIResource{ {Name: "deployments", Namespaced: true, Kind: "Deployment"}, }, } response := func(numErrors int) http.HandlerFunc { var i = 0 return func(w http.ResponseWriter, req *http.Request) { var list interface{} switch req.URL.Path { case "/apis/extensions/v1beta1": if i < numErrors { i++ w.WriteHeader(http.StatusInternalServerError) return } list = &beta case "/api/v1": list = &stable case "/api": list = &unversioned.APIVersions{ Versions: []string{ "v1", }, } case "/apis": list = &unversioned.APIGroupList{ Groups: []unversioned.APIGroup{ { Name: "extensions", Versions: []unversioned.GroupVersionForDiscovery{ {GroupVersion: "extensions/v1beta1"}, }, PreferredVersion: unversioned.GroupVersionForDiscovery{ GroupVersion: "extensions/v1beta1", Version:      "v1beta1", }, }, }, } default: t.Logf("unexpected request: %s", req.URL.Path) w.WriteHeader(http.StatusNotFound) return } output, err := json.Marshal(list) if err != nil { t.Errorf("unexpected encoding error: %v", err) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(output) } } tests := []struct { responseErrors  int expectResources int expectedError   func(err error) bool }{ { responseErrors:  1, expectResources: 2, expectedError: func(err error) bool { return err == nil }, }, { responseErrors:  2, expectResources: 1, expectedError:   IsGroupDiscoveryFailedError, }, } for i, tc := range tests { server := httptest.NewServer(http.HandlerFunc(response(tc.responseErrors))) defer server.Close() client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) got, err := client.ServerPreferredResources() if !tc.expectedError(err) { t.Errorf("case %d: unexpected error: %v", i, err) } if len(got) != tc.expectResources { t.Errorf("case %d: expect %d resources, got %#v", i, tc.expectResources, got) } server.Close() } } </ins>