mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-05 23:47:50 +00:00
Prepare for content-type negotiation
Combine the fields that will be used for content transformation (content-type, codec, and group version) into a single struct in client, and then pass that struct into the rest client and request. Set the content-type when sending requests to the server, and accept the content type as primary. Will form the foundation for content-negotiation via the client.
This commit is contained in:
@@ -70,7 +70,7 @@ func (c *RESTClient) Delete() *unversioned.Request {
|
||||
}
|
||||
|
||||
func (c *RESTClient) request(verb string) *unversioned.Request {
|
||||
return unversioned.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", *testapi.Default.GroupVersion(), c.Codec, nil)
|
||||
return unversioned.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: c.Codec}, nil)
|
||||
}
|
||||
|
||||
func (c *RESTClient) Do(req *http.Request) (*http.Response, error) {
|
||||
|
||||
@@ -55,14 +55,13 @@ type Config struct {
|
||||
Host string
|
||||
// APIPath is a sub-path that points to an API root.
|
||||
APIPath string
|
||||
// GroupVersion is the API version to talk to. Must be provided when initializing
|
||||
// a RESTClient directly. When initializing a Client, will be set with the default
|
||||
// code version.
|
||||
GroupVersion *unversioned.GroupVersion
|
||||
// Codec specifies the encoding and decoding behavior for runtime.Objects passed
|
||||
// to a RESTClient or Client. Required when initializing a RESTClient, optional
|
||||
// when initializing a Client.
|
||||
Codec runtime.Codec
|
||||
// Prefix is the sub path of the server. If not specified, the client will set
|
||||
// a default value. Use "/" to indicate the server root should be used
|
||||
Prefix string
|
||||
|
||||
// ContentConfig contains settings that affect how objects are transformed when
|
||||
// sent to the server.
|
||||
ContentConfig
|
||||
|
||||
// Server requires Basic authentication
|
||||
Username string
|
||||
@@ -120,6 +119,22 @@ type TLSClientConfig struct {
|
||||
CAData []byte
|
||||
}
|
||||
|
||||
type ContentConfig struct {
|
||||
// ContentType specifies the wire format used to communicate with the server.
|
||||
// This value will be set as the Accept header on requests made to the server, and
|
||||
// as the default content type on any object sent to the server. If not set,
|
||||
// "application/json" is used.
|
||||
ContentType string
|
||||
// GroupVersion is the API version to talk to. Must be provided when initializing
|
||||
// a RESTClient directly. When initializing a Client, will be set with the default
|
||||
// code version.
|
||||
GroupVersion *unversioned.GroupVersion
|
||||
// Codec specifies the encoding and decoding behavior for runtime.Objects passed
|
||||
// to a RESTClient or Client. Required when initializing a RESTClient, optional
|
||||
// when initializing a Client.
|
||||
Codec runtime.Codec
|
||||
}
|
||||
|
||||
// New creates a Kubernetes client for the given config. This client works with pods,
|
||||
// replication controllers, daemons, and services. It allows operations such as list, get, update
|
||||
// and delete on these objects. An error is returned if the provided configuration
|
||||
@@ -407,16 +422,18 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionedAPIPath, *config.GroupVersion, config.Codec, config.QPS, config.Burst)
|
||||
|
||||
transport, err := TransportFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
if transport != http.DefaultTransport {
|
||||
client.Client = &http.Client{Transport: transport}
|
||||
httpClient = &http.Client{Transport: transport}
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, config.QPS, config.Burst, httpClient)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -432,16 +449,23 @@ func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionedAPIPath, unversioned.SchemeGroupVersion, config.Codec, config.QPS, config.Burst)
|
||||
|
||||
transport, err := TransportFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
if transport != http.DefaultTransport {
|
||||
client.Client = &http.Client{Transport: transport}
|
||||
httpClient = &http.Client{Transport: transport}
|
||||
}
|
||||
|
||||
versionConfig := config.ContentConfig
|
||||
if versionConfig.GroupVersion == nil {
|
||||
v := unversioned.SchemeGroupVersion
|
||||
versionConfig.GroupVersion = &v
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionedAPIPath, versionConfig, config.QPS, config.Burst, httpClient)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -70,21 +70,21 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "explicit version supported",
|
||||
config: &unversioned.Config{GroupVersion: testapi.Default.GroupVersion()},
|
||||
config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}},
|
||||
serverVersions: []string{"/version1", testapi.Default.GroupVersion().String()},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()},
|
||||
expectedVersion: testapi.Default.GroupVersion(),
|
||||
},
|
||||
{
|
||||
name: "explicit version not supported",
|
||||
config: &unversioned.Config{GroupVersion: testapi.Default.GroupVersion()},
|
||||
config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) },
|
||||
},
|
||||
{
|
||||
name: "connection refused error",
|
||||
config: &unversioned.Config{GroupVersion: testapi.Default.GroupVersion()},
|
||||
config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}},
|
||||
serverVersions: []string{"version1"},
|
||||
clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()},
|
||||
sendErr: errors.New("connection refused"),
|
||||
|
||||
@@ -92,11 +92,13 @@ func TestSetKubernetesDefaults(t *testing.T) {
|
||||
{
|
||||
Config{},
|
||||
Config{
|
||||
APIPath: "/api",
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
QPS: 5,
|
||||
Burst: 10,
|
||||
APIPath: "/api",
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
},
|
||||
QPS: 5,
|
||||
Burst: 10,
|
||||
},
|
||||
false,
|
||||
},
|
||||
@@ -188,7 +190,7 @@ func TestHelperGetServerAPIVersions(t *testing.T) {
|
||||
}))
|
||||
// TODO: Uncomment when fix #19254
|
||||
// defer server.Close()
|
||||
got, err := ServerAPIVersions(&Config{Host: server.URL, GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, Codec: testapi.Default.Codec()})
|
||||
got, err := ServerAPIVersions(&Config{Host: server.URL, ContentConfig: ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, Codec: testapi.Default.Codec()}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
}
|
||||
@@ -208,7 +210,7 @@ func TestSetsCodec(t *testing.T) {
|
||||
// "invalidVersion": {true, "", nil},
|
||||
}
|
||||
for version, expected := range testCases {
|
||||
client, err := New(&Config{Host: "127.0.0.1", GroupVersion: &unversioned.GroupVersion{Version: version}})
|
||||
client, err := New(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
@@ -222,20 +224,20 @@ func TestSetsCodec(t *testing.T) {
|
||||
if e, a := expected.Prefix, client.RESTClient.versionedAPIPath; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
if e, a := expected.Codec, client.RESTClient.Codec; !reflect.DeepEqual(e, a) {
|
||||
if e, a := expected.Codec, client.RESTClient.contentConfig.Codec; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTClientRequires(t *testing.T) {
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Codec: testapi.Default.Codec()}); err == nil {
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{Codec: testapi.Default.Codec()}}); err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", GroupVersion: testapi.Default.GroupVersion()}); err == nil {
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}); err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}); err != nil {
|
||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}}); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func TestRequestExecuteRemoteCommand(t *testing.T) {
|
||||
server := httptest.NewServer(fakeExecServer(t, i, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount))
|
||||
|
||||
url, _ := url.ParseRequestURI(server.URL)
|
||||
c := client.NewRESTClient(url, "", unversioned.GroupVersion{Group: "x"}, nil, -1, -1)
|
||||
c := client.NewRESTClient(url, "", client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "x"}}, -1, -1, nil)
|
||||
req := c.Post().Resource("testing")
|
||||
req.SetHeader(httpstream.HeaderProtocolVersion, StreamProtocolV2Name)
|
||||
req.Param("command", "ls")
|
||||
@@ -275,7 +275,7 @@ func TestRequestAttachRemoteCommand(t *testing.T) {
|
||||
server := httptest.NewServer(fakeExecServer(t, i, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, 1))
|
||||
|
||||
url, _ := url.ParseRequestURI(server.URL)
|
||||
c := client.NewRESTClient(url, "", unversioned.GroupVersion{Group: "x"}, nil, -1, -1)
|
||||
c := client.NewRESTClient(url, "", client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "x"}}, -1, -1, nil)
|
||||
req := c.Post().Resource("testing")
|
||||
|
||||
conf := &client.Config{
|
||||
|
||||
@@ -81,10 +81,11 @@ func (r *RequestConstructionError) Error() string {
|
||||
// check once.
|
||||
type Request struct {
|
||||
// required
|
||||
client HTTPClient
|
||||
verb string
|
||||
client HTTPClient
|
||||
verb string
|
||||
|
||||
baseURL *url.URL
|
||||
codec runtime.Codec
|
||||
content ContentConfig
|
||||
|
||||
// generic components accessible via method setters
|
||||
pathPrefix string
|
||||
@@ -101,8 +102,6 @@ type Request struct {
|
||||
selector labels.Selector
|
||||
timeout time.Duration
|
||||
|
||||
groupVersion unversioned.GroupVersion
|
||||
|
||||
// output
|
||||
err error
|
||||
body io.Reader
|
||||
@@ -115,26 +114,28 @@ type Request struct {
|
||||
}
|
||||
|
||||
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
|
||||
func NewRequest(client HTTPClient, verb string, baseURL *url.URL, versionedAPIPath string, groupVersion unversioned.GroupVersion, codec runtime.Codec, backoff BackoffManager) *Request {
|
||||
func NewRequest(client HTTPClient, verb string, baseURL *url.URL, versionedAPIPath string, content ContentConfig, backoff BackoffManager) *Request {
|
||||
if backoff == nil {
|
||||
glog.V(2).Infof("Not implementing request backoff strategy.")
|
||||
backoff = &NoBackoff{}
|
||||
}
|
||||
metrics.Register()
|
||||
|
||||
pathPrefix := "/"
|
||||
if baseURL != nil {
|
||||
pathPrefix = path.Join(pathPrefix, baseURL.Path)
|
||||
}
|
||||
return &Request{
|
||||
client: client,
|
||||
verb: verb,
|
||||
baseURL: baseURL,
|
||||
pathPrefix: path.Join(pathPrefix, versionedAPIPath),
|
||||
groupVersion: groupVersion,
|
||||
codec: codec,
|
||||
backoffMgr: backoff,
|
||||
r := &Request{
|
||||
client: client,
|
||||
verb: verb,
|
||||
baseURL: baseURL,
|
||||
pathPrefix: path.Join(pathPrefix, versionedAPIPath),
|
||||
content: content,
|
||||
backoffMgr: backoff,
|
||||
}
|
||||
if len(content.ContentType) > 0 {
|
||||
r.SetHeader("Accept", content.ContentType+", */*")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Prefix adds segments to the relative beginning to the request path. These
|
||||
@@ -323,8 +324,8 @@ func (r resourceTypeToFieldMapping) filterField(resourceType, field, value strin
|
||||
|
||||
type versionToResourceToFieldMapping map[unversioned.GroupVersion]resourceTypeToFieldMapping
|
||||
|
||||
func (v versionToResourceToFieldMapping) filterField(groupVersion unversioned.GroupVersion, resourceType, field, value string) (newField, newValue string, err error) {
|
||||
rMapping, ok := v[groupVersion]
|
||||
func (v versionToResourceToFieldMapping) filterField(groupVersion *unversioned.GroupVersion, resourceType, field, value string) (newField, newValue string, err error) {
|
||||
rMapping, ok := v[*groupVersion]
|
||||
if !ok {
|
||||
glog.Warningf("Field selector: %v - %v - %v - %v: need to check if this is versioned correctly.", groupVersion, resourceType, field, value)
|
||||
return field, value, nil
|
||||
@@ -384,13 +385,13 @@ func (r *Request) FieldsSelectorParam(s fields.Selector) *Request {
|
||||
return r
|
||||
}
|
||||
s2, err := s.Transform(func(field, value string) (newField, newValue string, err error) {
|
||||
return fieldMappings.filterField(r.groupVersion, r.resource, field, value)
|
||||
return fieldMappings.filterField(r.content.GroupVersion, r.resource, field, value)
|
||||
})
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
return r.setParam(unversioned.FieldSelectorQueryParam(r.groupVersion.String()), s2.String())
|
||||
return r.setParam(unversioned.FieldSelectorQueryParam(r.content.GroupVersion.String()), s2.String())
|
||||
}
|
||||
|
||||
// LabelsSelectorParam adds the given selector as a query parameter
|
||||
@@ -404,7 +405,7 @@ func (r *Request) LabelsSelectorParam(s labels.Selector) *Request {
|
||||
if s.Empty() {
|
||||
return r
|
||||
}
|
||||
return r.setParam(unversioned.LabelSelectorQueryParam(r.groupVersion.String()), s.String())
|
||||
return r.setParam(unversioned.LabelSelectorQueryParam(r.content.GroupVersion.String()), s.String())
|
||||
}
|
||||
|
||||
// UintParam creates a query parameter with the given value.
|
||||
@@ -430,7 +431,7 @@ func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectCo
|
||||
if r.err != nil {
|
||||
return r
|
||||
}
|
||||
versioned, err := convertor.ConvertToVersion(obj, r.groupVersion.String())
|
||||
versioned, err := convertor.ConvertToVersion(obj, r.content.GroupVersion.String())
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
@@ -444,14 +445,14 @@ func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectCo
|
||||
for _, value := range v {
|
||||
// TODO: Move it to setParam method, once we get rid of
|
||||
// FieldSelectorParam & LabelSelectorParam methods.
|
||||
if k == unversioned.LabelSelectorQueryParam(r.groupVersion.String()) && value == "" {
|
||||
if k == unversioned.LabelSelectorQueryParam(r.content.GroupVersion.String()) && value == "" {
|
||||
// Don't set an empty selector for backward compatibility.
|
||||
// Since there is no way to get the difference between empty
|
||||
// and unspecified string, we don't set it to avoid having
|
||||
// labelSelector= param in every request.
|
||||
continue
|
||||
}
|
||||
if k == unversioned.FieldSelectorQueryParam(r.groupVersion.String()) {
|
||||
if k == unversioned.FieldSelectorQueryParam(r.content.GroupVersion.String()) {
|
||||
if len(value) == 0 {
|
||||
// Don't set an empty selector for backward compatibility.
|
||||
// Since there is no way to get the difference between empty
|
||||
@@ -467,7 +468,7 @@ func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectCo
|
||||
}
|
||||
filteredSelector, err := selector.Transform(
|
||||
func(field, value string) (newField, newValue string, err error) {
|
||||
return fieldMappings.filterField(r.groupVersion, r.resource, field, value)
|
||||
return fieldMappings.filterField(r.content.GroupVersion, r.resource, field, value)
|
||||
})
|
||||
if err != nil {
|
||||
r.err = fmt.Errorf("untransformable field selector: %v", err)
|
||||
@@ -542,14 +543,14 @@ func (r *Request) Body(obj interface{}) *Request {
|
||||
if reflect.ValueOf(t).IsNil() {
|
||||
return r
|
||||
}
|
||||
data, err := runtime.Encode(r.codec, t)
|
||||
data, err := runtime.Encode(r.content.Codec, t)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
glog.V(8).Infof("Request Body: %s", string(data))
|
||||
r.body = bytes.NewBuffer(data)
|
||||
r.SetHeader("Content-Type", "application/json")
|
||||
r.SetHeader("Content-Type", r.content.ContentType)
|
||||
default:
|
||||
r.err = fmt.Errorf("unknown type used for body: %+v", obj)
|
||||
}
|
||||
@@ -652,7 +653,7 @@ func (r *Request) Watch() (watch.Interface, error) {
|
||||
}
|
||||
return nil, fmt.Errorf("for request '%+v', got status: %v", url, resp.StatusCode)
|
||||
}
|
||||
return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil
|
||||
return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.content.Codec)), nil
|
||||
}
|
||||
|
||||
// updateURLMetrics is a convenience function for pushing metrics.
|
||||
@@ -717,7 +718,7 @@ func (r *Request) Stream() (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("%v while accessing %v", resp.Status, url)
|
||||
}
|
||||
|
||||
if runtimeObject, err := runtime.Decode(r.codec, bodyBytes); err == nil {
|
||||
if runtimeObject, err := runtime.Decode(r.content.Codec, bodyBytes); err == nil {
|
||||
statusError := errors.FromObject(runtimeObject)
|
||||
|
||||
if _, ok := statusError.(errors.APIStatus); ok {
|
||||
@@ -847,7 +848,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
// Did the server give us a status response?
|
||||
isStatusResponse := false
|
||||
var status *unversioned.Status
|
||||
result, err := runtime.Decode(r.codec, body)
|
||||
result, err := runtime.Decode(r.content.Codec, body)
|
||||
if out, ok := result.(*unversioned.Status); err == nil && ok && len(out.Status) > 0 {
|
||||
status = out
|
||||
isStatusResponse = true
|
||||
@@ -871,9 +872,10 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
}
|
||||
|
||||
return Result{
|
||||
body: body,
|
||||
statusCode: resp.StatusCode,
|
||||
codec: r.codec,
|
||||
body: body,
|
||||
contentType: resp.Header.Get("Content-Type"),
|
||||
statusCode: resp.StatusCode,
|
||||
decoder: r.content.Codec,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,7 +910,18 @@ func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *h
|
||||
message = strings.TrimSpace(string(body))
|
||||
}
|
||||
retryAfter, _ := retryAfterSeconds(resp)
|
||||
return errors.NewGenericServerResponse(resp.StatusCode, req.Method, unversioned.GroupResource{Group: r.groupVersion.Group, Resource: r.resource}, r.resourceName, message, retryAfter, true)
|
||||
return errors.NewGenericServerResponse(
|
||||
resp.StatusCode,
|
||||
req.Method,
|
||||
unversioned.GroupResource{
|
||||
Group: r.content.GroupVersion.Group,
|
||||
Resource: r.resource,
|
||||
},
|
||||
r.resourceName,
|
||||
message,
|
||||
retryAfter,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// isTextResponse returns true if the response appears to be a textual media type.
|
||||
@@ -950,11 +963,12 @@ func retryAfterSeconds(resp *http.Response) (int, bool) {
|
||||
|
||||
// Result contains the result of calling Request.Do().
|
||||
type Result struct {
|
||||
body []byte
|
||||
err error
|
||||
statusCode int
|
||||
body []byte
|
||||
contentType string
|
||||
err error
|
||||
statusCode int
|
||||
|
||||
codec runtime.Codec
|
||||
decoder runtime.Decoder
|
||||
}
|
||||
|
||||
// Raw returns the raw result.
|
||||
@@ -967,8 +981,7 @@ func (r Result) Get() (runtime.Object, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
obj, err := runtime.Decode(r.codec, r.body)
|
||||
return obj, err
|
||||
return runtime.Decode(r.decoder, r.body)
|
||||
}
|
||||
|
||||
// StatusCode returns the HTTP status code of the request. (Only valid if no
|
||||
@@ -983,7 +996,7 @@ func (r Result) Into(obj runtime.Object) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
return runtime.DecodeInto(r.codec, r.body, obj)
|
||||
return runtime.DecodeInto(r.decoder, r.body, obj)
|
||||
}
|
||||
|
||||
// WasCreated updates the provided bool pointer to whether the server returned
|
||||
|
||||
@@ -45,10 +45,21 @@ import (
|
||||
watchjson "k8s.io/kubernetes/pkg/watch/json"
|
||||
)
|
||||
|
||||
func TestNewRequestSetsAccept(t *testing.T) {
|
||||
r := NewRequest(nil, "get", &url.URL{Path: "/path/"}, "", ContentConfig{}, nil)
|
||||
if r.headers.Get("Accept") != "" {
|
||||
t.Errorf("unexpected headers: %#v", r.headers)
|
||||
}
|
||||
r = NewRequest(nil, "get", &url.URL{Path: "/path/"}, "", ContentConfig{ContentType: "application/other"}, nil)
|
||||
if r.headers.Get("Accept") != "application/other, */*" {
|
||||
t.Errorf("unexpected headers: %#v", r.headers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestWithErrorWontChange(t *testing.T) {
|
||||
original := Request{
|
||||
err: errors.New("test"),
|
||||
groupVersion: *testapi.Default.GroupVersion(),
|
||||
err: errors.New("test"),
|
||||
content: ContentConfig{GroupVersion: testapi.Default.GroupVersion()},
|
||||
}
|
||||
r := original
|
||||
changed := r.Param("foo", "bar").
|
||||
@@ -179,7 +190,7 @@ func TestRequestParam(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequestVersionedParams(t *testing.T) {
|
||||
r := (&Request{groupVersion: v1.SchemeGroupVersion}).Param("foo", "a")
|
||||
r := (&Request{content: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}).Param("foo", "a")
|
||||
if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) {
|
||||
t.Errorf("should have set a param: %#v", r)
|
||||
}
|
||||
@@ -195,7 +206,7 @@ func TestRequestVersionedParams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequestVersionedParamsFromListOptions(t *testing.T) {
|
||||
r := &Request{groupVersion: v1.SchemeGroupVersion}
|
||||
r := &Request{content: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}
|
||||
r.VersionedParams(&api.ListOptions{ResourceVersion: "1"}, api.Scheme)
|
||||
if !reflect.DeepEqual(r.params, url.Values{
|
||||
"resourceVersion": []string{"1"},
|
||||
@@ -250,7 +261,7 @@ func TestRequestBody(t *testing.T) {
|
||||
}
|
||||
|
||||
// test unencodable api object
|
||||
r = (&Request{codec: testapi.Default.Codec()}).Body(&NotAnAPIObject{})
|
||||
r = (&Request{content: ContentConfig{Codec: testapi.Default.Codec()}}).Body(&NotAnAPIObject{})
|
||||
if r.err == nil || r.body != nil {
|
||||
t.Errorf("should have set err and left body nil: %#v", r)
|
||||
}
|
||||
@@ -265,7 +276,7 @@ func TestResultIntoWithErrReturnsErr(t *testing.T) {
|
||||
|
||||
func TestURLTemplate(t *testing.T) {
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
r := NewRequest(nil, "POST", uri, "", unversioned.GroupVersion{Group: "test"}, nil, nil)
|
||||
r := NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "test"}}, nil)
|
||||
r.Prefix("pre1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0")
|
||||
full := r.URL()
|
||||
if full.String() != "http://localhost/pre1/namespaces/ns/r1/nm?p0=v0" {
|
||||
@@ -326,7 +337,7 @@ func TestTransformResponse(t *testing.T) {
|
||||
{Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
r := NewRequest(nil, "", uri, "", *testapi.Default.GroupVersion(), testapi.Default.Codec(), nil)
|
||||
r := NewRequest(nil, "", uri, "", ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}, nil)
|
||||
if test.Response.Body == nil {
|
||||
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
}
|
||||
@@ -413,7 +424,7 @@ func TestTransformUnstructuredError(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
r := &Request{
|
||||
codec: testapi.Default.Codec(),
|
||||
content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()},
|
||||
resourceName: testCase.Name,
|
||||
resource: testCase.Resource,
|
||||
}
|
||||
@@ -464,7 +475,7 @@ func TestRequestWatch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Request: &Request{
|
||||
codec: testapi.Default.Codec(),
|
||||
content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()},
|
||||
client: clientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: http.StatusForbidden}, nil
|
||||
}),
|
||||
@@ -477,7 +488,7 @@ func TestRequestWatch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Request: &Request{
|
||||
codec: testapi.Default.Codec(),
|
||||
content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()},
|
||||
client: clientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: http.StatusUnauthorized}, nil
|
||||
}),
|
||||
@@ -490,7 +501,7 @@ func TestRequestWatch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Request: &Request{
|
||||
codec: testapi.Default.Codec(),
|
||||
content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()},
|
||||
client: clientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
@@ -602,7 +613,7 @@ func TestRequestStream(t *testing.T) {
|
||||
})))),
|
||||
}, nil
|
||||
}),
|
||||
codec: testapi.Default.Codec(),
|
||||
content: ContentConfig{Codec: testapi.Default.Codec()},
|
||||
baseURL: &url.URL{},
|
||||
},
|
||||
Err: true,
|
||||
@@ -1109,7 +1120,7 @@ func TestUintParam(t *testing.T) {
|
||||
|
||||
for _, item := range table {
|
||||
u, _ := url.Parse("http://localhost")
|
||||
r := NewRequest(nil, "GET", u, "", unversioned.GroupVersion{Group: "test"}, nil, nil).AbsPath("").UintParam(item.name, item.testVal)
|
||||
r := NewRequest(nil, "GET", u, "", ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "test"}}, nil).AbsPath("").UintParam(item.name, item.testVal)
|
||||
if e, a := item.expectStr, r.URL().String(); e != a {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
@@ -1149,6 +1160,8 @@ func TestBody(t *testing.T) {
|
||||
}
|
||||
f.Close()
|
||||
|
||||
var nilObject *api.DeleteOptions
|
||||
typedObject := interface{}(nilObject)
|
||||
c := testRESTClient(t, nil)
|
||||
tests := []struct {
|
||||
input interface{}
|
||||
@@ -1159,6 +1172,7 @@ func TestBody(t *testing.T) {
|
||||
{f.Name(), data, nil},
|
||||
{strings.NewReader(data), data, nil},
|
||||
{obj, string(bodyExpected), map[string]string{"Content-Type": "application/json"}},
|
||||
{typedObject, "", nil},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r := c.Post().Body(tt.input)
|
||||
@@ -1166,6 +1180,20 @@ func TestBody(t *testing.T) {
|
||||
t.Errorf("%d: r.Body(%#v) error: %v", i, tt, r.err)
|
||||
continue
|
||||
}
|
||||
if tt.headers != nil {
|
||||
for k, v := range tt.headers {
|
||||
if r.headers.Get(k) != v {
|
||||
t.Errorf("%d: r.headers[%q] = %q; want %q", i, k, v, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.body == nil {
|
||||
if len(tt.expected) != 0 {
|
||||
t.Errorf("%d: r.body = %q; want %q", i, r.body, tt.expected)
|
||||
}
|
||||
continue
|
||||
}
|
||||
buf := make([]byte, len(tt.expected))
|
||||
if _, err := r.body.Read(buf); err != nil {
|
||||
t.Errorf("%d: r.body.Read error: %v", i, err)
|
||||
@@ -1175,13 +1203,6 @@ func TestBody(t *testing.T) {
|
||||
if body != tt.expected {
|
||||
t.Errorf("%d: r.body = %q; want %q", i, body, tt.expected)
|
||||
}
|
||||
if tt.headers != nil {
|
||||
for k, v := range tt.headers {
|
||||
if r.headers.Get(k) != v {
|
||||
t.Errorf("%d: r.headers[%q] = %q; want %q", i, k, v, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,5 +1303,5 @@ func testRESTClient(t testing.TB, srv *httptest.Server) *RESTClient {
|
||||
}
|
||||
}
|
||||
versionedAPIPath := testapi.Default.ResourcePath("", "", "")
|
||||
return NewRESTClient(baseURL, versionedAPIPath, *testapi.Default.GroupVersion(), testapi.Default.Codec(), 0, 0)
|
||||
return NewRESTClient(baseURL, versionedAPIPath, ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}, 0, 0, nil)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
@@ -45,27 +44,25 @@ const (
|
||||
//
|
||||
// Most consumers should use client.New() to get a Kubernetes API client.
|
||||
type RESTClient struct {
|
||||
baseURL *url.URL
|
||||
// base is the root URL for all invocations of the client
|
||||
base *url.URL
|
||||
// versionedAPIPath is a path segment connecting the base URL to the resource root
|
||||
versionedAPIPath string
|
||||
// A string identifying the version of the API this client is expected to use.
|
||||
groupVersion unversioned.GroupVersion
|
||||
|
||||
// Codec is the encoding and decoding scheme that applies to a particular set of
|
||||
// REST resources.
|
||||
Codec runtime.Codec
|
||||
|
||||
// Set specific behavior of the client. If not set http.DefaultClient will be
|
||||
// used.
|
||||
Client *http.Client
|
||||
// contentConfig is the information used to communicate with the server.
|
||||
contentConfig ContentConfig
|
||||
|
||||
// TODO extract this into a wrapper interface via the RESTClient interface in kubectl.
|
||||
Throttle util.RateLimiter
|
||||
|
||||
// Set specific behavior of the client. If not set http.DefaultClient will be used.
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
|
||||
// such as Get, Put, Post, and Delete on specified paths. Codec controls encoding and
|
||||
// decoding of responses from the server.
|
||||
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, groupVersion unversioned.GroupVersion, c runtime.Codec, maxQPS float32, maxBurst int) *RESTClient {
|
||||
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConfig, maxQPS float32, maxBurst int, client *http.Client) *RESTClient {
|
||||
base := *baseURL
|
||||
if !strings.HasSuffix(base.Path, "/") {
|
||||
base.Path += "/"
|
||||
@@ -73,16 +70,23 @@ func NewRESTClient(baseURL *url.URL, versionedAPIPath string, groupVersion unver
|
||||
base.RawQuery = ""
|
||||
base.Fragment = ""
|
||||
|
||||
if config.GroupVersion == nil {
|
||||
config.GroupVersion = &unversioned.GroupVersion{}
|
||||
}
|
||||
if len(config.ContentType) == 0 {
|
||||
config.ContentType = "application/json"
|
||||
}
|
||||
|
||||
var throttle util.RateLimiter
|
||||
if maxQPS > 0 {
|
||||
throttle = util.NewTokenBucketRateLimiter(maxQPS, maxBurst)
|
||||
}
|
||||
return &RESTClient{
|
||||
baseURL: &base,
|
||||
base: &base,
|
||||
versionedAPIPath: versionedAPIPath,
|
||||
groupVersion: groupVersion,
|
||||
Codec: c,
|
||||
contentConfig: config,
|
||||
Throttle: throttle,
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +101,11 @@ func readExpBackoffConfig() BackoffManager {
|
||||
backoffDurationInt, errDuration := strconv.ParseInt(backoffDuration, 10, 64)
|
||||
if errBase != nil || errDuration != nil {
|
||||
return &NoBackoff{}
|
||||
} else {
|
||||
return &URLBackoff{
|
||||
Backoff: util.NewBackOff(
|
||||
time.Duration(backoffBaseInt)*time.Second,
|
||||
time.Duration(backoffDurationInt)*time.Second)}
|
||||
}
|
||||
return &URLBackoff{
|
||||
Backoff: util.NewBackOff(
|
||||
time.Duration(backoffBaseInt)*time.Second,
|
||||
time.Duration(backoffDurationInt)*time.Second)}
|
||||
}
|
||||
|
||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||
@@ -125,9 +128,9 @@ func (c *RESTClient) Verb(verb string) *Request {
|
||||
backoff := readExpBackoffConfig()
|
||||
|
||||
if c.Client == nil {
|
||||
return NewRequest(nil, verb, c.baseURL, c.versionedAPIPath, c.groupVersion, c.Codec, backoff)
|
||||
return NewRequest(nil, verb, c.base, c.versionedAPIPath, c.contentConfig, backoff)
|
||||
}
|
||||
return NewRequest(c.Client, verb, c.baseURL, c.versionedAPIPath, c.groupVersion, c.Codec, backoff)
|
||||
return NewRequest(c.Client, verb, c.base, c.versionedAPIPath, c.contentConfig, backoff)
|
||||
}
|
||||
|
||||
// Post begins a POST request. Short for c.Verb("POST").
|
||||
@@ -157,5 +160,5 @@ func (c *RESTClient) Delete() *Request {
|
||||
|
||||
// APIVersion returns the APIVersion this RESTClient is expected to use.
|
||||
func (c *RESTClient) APIVersion() unversioned.GroupVersion {
|
||||
return c.groupVersion
|
||||
return *c.contentConfig.GroupVersion
|
||||
}
|
||||
|
||||
@@ -45,11 +45,13 @@ func TestDoRequestSuccess(t *testing.T) {
|
||||
// TODO: Uncomment when fix #19254
|
||||
// defer testServer.Close()
|
||||
c, err := RESTClientFor(&Config{
|
||||
Host: testServer.URL,
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
Host: testServer.URL,
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
},
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -89,9 +91,11 @@ func TestDoRequestFailed(t *testing.T) {
|
||||
// TODO: Uncomment when fix #19254
|
||||
// defer testServer.Close()
|
||||
c, err := RESTClientFor(&Config{
|
||||
Host: testServer.URL,
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
Host: testServer.URL,
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -122,11 +126,13 @@ func TestDoRequestCreated(t *testing.T) {
|
||||
// TODO: Uncomment when fix #19254
|
||||
// defer testServer.Close()
|
||||
c, err := RESTClientFor(&Config{
|
||||
Host: testServer.URL,
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
Host: testServer.URL,
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Codec: testapi.Default.Codec(),
|
||||
},
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
|
||||
@@ -76,15 +76,15 @@ func (c *Client) Setup(t *testing.T) *Client {
|
||||
c.server = httptest.NewServer(c.handler)
|
||||
if c.Client == nil {
|
||||
c.Client = client.NewOrDie(&client.Config{
|
||||
Host: c.server.URL,
|
||||
GroupVersion: testapi.Default.GroupVersion(),
|
||||
Host: c.server.URL,
|
||||
ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()},
|
||||
})
|
||||
|
||||
// TODO: caesarxuchao: hacky way to specify version of Experimental client.
|
||||
// We will fix this by supporting multiple group versions in Config
|
||||
c.ExtensionsClient = client.NewExtensionsOrDie(&client.Config{
|
||||
Host: c.server.URL,
|
||||
GroupVersion: testapi.Extensions.GroupVersion(),
|
||||
Host: c.server.URL,
|
||||
ContentConfig: client.ContentConfig{GroupVersion: testapi.Extensions.GroupVersion()},
|
||||
})
|
||||
}
|
||||
c.QueryValidator = map[string]func(string, string) bool{}
|
||||
|
||||
Reference in New Issue
Block a user