Merge pull request #35799 from deads2k/client-17-negotiation
Automatic merge from submit-queue clean up client version negotiation to handle no legacy API Version negotiation fails if the legacy API endpoint isn't available. This tightens up the negotiation interface based to more clearly express what each stage is doing and what the constraints on negotiation are. This is needed to speak to generic API servers. @kubernetes/kubectl
This commit is contained in:
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
// Import solely to initialize client auth plugins.
|
||||
@@ -30,14 +29,7 @@ import (
|
||||
// MatchesServerVersion queries the server to compares the build version
|
||||
// (git hash) of the client with the server's build version. It returns an error
|
||||
// if it failed to contact the server or if the versions are not an exact match.
|
||||
func MatchesServerVersion(client DiscoveryInterface, c *restclient.Config) error {
|
||||
var err error
|
||||
if client == nil {
|
||||
client, err = NewDiscoveryClientForConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
func MatchesServerVersion(client DiscoveryInterface) error {
|
||||
cVer := version.Get()
|
||||
sVer, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
@@ -55,19 +47,9 @@ func MatchesServerVersion(client DiscoveryInterface, c *restclient.Config) error
|
||||
// a version that both client and server support.
|
||||
// - If no version is provided, try registered client versions in order of
|
||||
// preference.
|
||||
// - If version is provided, but not default config (explicitly requested via
|
||||
// commandline flag), and is unsupported by the server, print a warning to
|
||||
// stderr and try client's registered versions in order of preference.
|
||||
// - If version is config default, and the server does not support it,
|
||||
// - If version is provided and the server does not support it,
|
||||
// return an error.
|
||||
func NegotiateVersion(client DiscoveryInterface, c *restclient.Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) {
|
||||
var err error
|
||||
if client == nil {
|
||||
client, err = NewDiscoveryClientForConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func NegotiateVersion(client DiscoveryInterface, requiredGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) {
|
||||
clientVersions := sets.String{}
|
||||
for _, gv := range clientRegisteredGVs {
|
||||
clientVersions.Insert(gv.String())
|
||||
@@ -84,37 +66,23 @@ func NegotiateVersion(client DiscoveryInterface, c *restclient.Config, requested
|
||||
serverVersions.Insert(v)
|
||||
}
|
||||
|
||||
// If no version requested, use config version (may also be empty).
|
||||
// make a copy of the original so we don't risk mutating input here or in the returned value
|
||||
var preferredGV *unversioned.GroupVersion
|
||||
switch {
|
||||
case requestedGV != nil:
|
||||
t := *requestedGV
|
||||
preferredGV = &t
|
||||
case c.GroupVersion != nil:
|
||||
t := *c.GroupVersion
|
||||
preferredGV = &t
|
||||
}
|
||||
|
||||
// If version explicitly requested verify that both client and server support it.
|
||||
// If server does not support warn, but try to negotiate a lower version.
|
||||
if preferredGV != nil {
|
||||
if !clientVersions.Has(preferredGV.String()) {
|
||||
return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientVersions)
|
||||
if requiredGV != nil {
|
||||
if !clientVersions.Has(requiredGV.String()) {
|
||||
return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions)
|
||||
|
||||
}
|
||||
// If the server supports no versions, then we should just use the preferredGV
|
||||
// This can happen because discovery fails due to 403 Forbidden errors
|
||||
if len(serverVersions) == 0 {
|
||||
return preferredGV, nil
|
||||
return requiredGV, nil
|
||||
}
|
||||
if serverVersions.Has(preferredGV.String()) {
|
||||
return preferredGV, nil
|
||||
if serverVersions.Has(requiredGV.String()) {
|
||||
return requiredGV, nil
|
||||
}
|
||||
// If we are using an explicit config version the server does not support, fail.
|
||||
if (c.GroupVersion != nil) && (*preferredGV == *c.GroupVersion) {
|
||||
return nil, fmt.Errorf("server does not support API version %q", preferredGV)
|
||||
}
|
||||
return nil, fmt.Errorf("server does not support API version %q", requiredGV)
|
||||
}
|
||||
|
||||
for _, clientGV := range clientRegisteredGVs {
|
||||
@@ -130,6 +98,12 @@ func NegotiateVersion(client DiscoveryInterface, c *restclient.Config, requested
|
||||
return &t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// if we have no server versions and we have no required version, choose the first clientRegisteredVersion
|
||||
if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 {
|
||||
return &clientRegisteredGVs[0], nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v",
|
||||
serverVersions, clientVersions)
|
||||
}
|
||||
|
@@ -47,19 +47,16 @@ func objBody(object interface{}) io.ReadCloser {
|
||||
func TestNegotiateVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version *uapi.GroupVersion
|
||||
requiredVersion *uapi.GroupVersion
|
||||
expectedVersion *uapi.GroupVersion
|
||||
serverVersions []string
|
||||
clientVersions []uapi.GroupVersion
|
||||
config *restclient.Config
|
||||
expectErr func(err error) bool
|
||||
sendErr error
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "server supports client default",
|
||||
version: &uapi.GroupVersion{Version: "version1"},
|
||||
config: &restclient.Config{},
|
||||
serverVersions: []string{"version1", registered.GroupOrDie(api.GroupName).GroupVersion.String()},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectedVersion: &uapi.GroupVersion{Version: "version1"},
|
||||
@@ -67,8 +64,6 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "server falls back to client supported",
|
||||
version: ®istered.GroupOrDie(api.GroupName).GroupVersion,
|
||||
config: &restclient.Config{},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectedVersion: &uapi.GroupVersion{Version: "version1"},
|
||||
@@ -76,23 +71,30 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "explicit version supported",
|
||||
config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}},
|
||||
requiredVersion: &uapi.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"/version1", registered.GroupOrDie(api.GroupName).GroupVersion.String()},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectedVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion,
|
||||
expectedVersion: &uapi.GroupVersion{Version: "v1"},
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "explicit version not supported",
|
||||
config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) },
|
||||
statusCode: http.StatusOK,
|
||||
name: "explicit version not supported on server",
|
||||
requiredVersion: &uapi.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "explicit version not supported on client",
|
||||
requiredVersion: &uapi.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"v1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `client does not support API version "v1"`) },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "connection refused error",
|
||||
config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
sendErr: errors.New("connection refused"),
|
||||
@@ -101,25 +103,21 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "discovery fails due to 403 Forbidden errors and thus serverVersions is empty, use default GroupVersion",
|
||||
config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectedVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion,
|
||||
expectedVersion: &uapi.GroupVersion{Version: "version1"},
|
||||
statusCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "discovery fails due to 404 Not Found errors and thus serverVersions is empty, use requested GroupVersion",
|
||||
version: &uapi.GroupVersion{Version: "version1"},
|
||||
config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}},
|
||||
requiredVersion: &uapi.GroupVersion{Version: "version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectedVersion: &uapi.GroupVersion{Version: "version1"},
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "discovery fails due to 403 Forbidden errors and thus serverVersions is empty, no fallback GroupVersion",
|
||||
config: &restclient.Config{},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, registered.GroupOrDie(api.GroupName).GroupVersion},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), "failed to negotiate an api version;") },
|
||||
statusCode: http.StatusForbidden,
|
||||
name: "discovery fails due to 403 Forbidden errors and thus serverVersions is empty, no fallback GroupVersion",
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), "failed to negotiate an api version;") },
|
||||
statusCode: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -139,9 +137,9 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
return &http.Response{StatusCode: test.statusCode, Header: header, Body: objBody(&uapi.APIVersions{Versions: test.serverVersions})}, nil
|
||||
}),
|
||||
}
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(test.config)
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(&restclient.Config{})
|
||||
c.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
||||
response, err := discovery.NegotiateVersion(c, test.config, test.version, test.clientVersions)
|
||||
response, err := discovery.NegotiateVersion(c, test.requiredVersion, test.clientVersions)
|
||||
if err == nil && test.expectErr != nil {
|
||||
t.Errorf("expected error, got nil for [%s].", test.name)
|
||||
}
|
||||
|
Reference in New Issue
Block a user