kubectl negotiates apiversion to use based on client,server supported

(cherry picked from commit f31191224b)
This commit is contained in:
Jeff Lowdermilk 2015-06-12 19:06:18 -07:00 committed by Brendan Burns
parent 4cd4d363c5
commit e2f4472d71
4 changed files with 105 additions and 27 deletions

View File

@ -30,8 +30,11 @@ import (
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog"
)
// Config holds the common attributes that can be passed to a Kubernetes client on
@ -143,6 +146,9 @@ func New(c *Config) (*Client, error) {
return &Client{client}, nil
}
// 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(c *Config) error {
client, err := New(c)
if err != nil {
@ -161,6 +167,66 @@ func MatchesServerVersion(c *Config) error {
return nil
}
// NegotiateVersion queries the server's supported api versions to find
// a version that both client and server support.
// - If no version is provided, try the client's registered 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,
// return an error.
func NegotiateVersion(c *Config, version string) (string, error) {
client, err := New(c)
if err != nil {
return "", err
}
clientVersions := util.StringSet{}
for _, v := range registered.RegisteredVersions {
clientVersions.Insert(v)
}
apiVersions, err := client.ServerAPIVersions()
if err != nil {
return "", fmt.Errorf("couldn't read version from server: %v\n", err)
}
serverVersions := util.StringSet{}
for _, v := range apiVersions.Versions {
serverVersions.Insert(v)
}
// If no version requested, use config version (may also be empty).
if len(version) == 0 {
version = c.Version
}
// 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 len(version) != 0 {
if !clientVersions.Has(version) {
return "", fmt.Errorf("Client does not support API version '%s'. Client supported API versions: %v", version, clientVersions)
}
if serverVersions.Has(version) {
return version, nil
}
// If we are using an explicit config version the server does not support, fail.
if version == c.Version {
return "", fmt.Errorf("Server does not support API version '%s'.", version)
}
}
for _, clientVersion := range registered.RegisteredVersions {
if serverVersions.Has(clientVersion) {
// Version was not explicitly requested in command config (--api-version).
// Ok to fall back to a supported version with a warning.
if len(version) != 0 {
glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion)
}
return clientVersion, nil
}
}
return "", fmt.Errorf("Failed to negotiate an api version. Server supports: %v. Client supports: %v.",
serverVersions, registered.RegisteredVersions)
}
// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
func NewOrDie(c *Config) *Client {
client, err := New(c)

View File

@ -221,23 +221,25 @@ func stringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
// TODO(jlowdermilk): refactor the Factory so we can test client versions properly,
// with different client/server version skew scenarios.
// Verify that resource.RESTClients constructed from a factory respect mapping.APIVersion
func TestClientVersions(t *testing.T) {
f := cmdutil.NewFactory(nil)
version := testapi.Version()
mapping := &meta.RESTMapping{
APIVersion: version,
}
c, err := f.RESTClient(mapping)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
client := c.(*client.RESTClient)
if client.APIVersion() != version {
t.Errorf("unexpected Client APIVersion: %s %v", client.APIVersion, client)
}
}
//func TestClientVersions(t *testing.T) {
// f := cmdutil.NewFactory(nil)
//
// version := testapi.Version()
// mapping := &meta.RESTMapping{
// APIVersion: version,
// }
// c, err := f.RESTClient(mapping)
// if err != nil {
// t.Errorf("unexpected error: %v", err)
// }
// client := c.(*client.RESTClient)
// if client.APIVersion() != version {
// t.Errorf("unexpected Client APIVersion: %s %v", client.APIVersion, client)
// }
//}
func ExamplePrintReplicationController() {
f, tf, codec := NewAPIFactory()

View File

@ -21,11 +21,20 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
)
func NewClientCache(loader clientcmd.ClientConfig) *clientCache {
return &clientCache{
clients: make(map[string]*client.Client),
configs: make(map[string]*client.Config),
loader: loader,
}
}
// clientCache caches previously loaded clients for reuse, and ensures MatchServerVersion
// is invoked only once
type clientCache struct {
loader clientcmd.ClientConfig
clients map[string]*client.Client
configs map[string]*client.Config
defaultConfig *client.Config
matchVersion bool
}
@ -44,12 +53,18 @@ func (c *clientCache) ClientConfigForVersion(version string) (*client.Config, er
}
}
}
if config, ok := c.configs[version]; ok {
return config, nil
}
// TODO: have a better config copy method
config := *c.defaultConfig
if len(version) != 0 {
config.Version = version
negotiatedVersion, err := client.NegotiateVersion(&config, version)
if err != nil {
return nil, err
}
config.Version = negotiatedVersion
client.SetKubernetesDefaults(&config)
c.configs[version] = &config
return &config, nil
}
@ -57,15 +72,13 @@ func (c *clientCache) ClientConfigForVersion(version string) (*client.Config, er
// ClientForVersion initializes or reuses a client for the specified version, or returns an
// error if that is not possible
func (c *clientCache) ClientForVersion(version string) (*client.Client, error) {
if client, ok := c.clients[version]; ok {
return client, nil
}
config, err := c.ClientConfigForVersion(version)
if err != nil {
return nil, err
}
if client, ok := c.clients[config.Version]; ok {
return client, nil
}
client, err := client.New(config)
if err != nil {
return nil, err

View File

@ -102,10 +102,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
clientConfig = DefaultClientConfig(flags)
}
clients := &clientCache{
clients: make(map[string]*client.Client),
loader: clientConfig,
}
clients := NewClientCache(clientConfig)
return &Factory{
clients: clients,