Merge pull request #9826 from brendandburns/redirect

Remove the redirect verb.
This commit is contained in:
Saad Ali 2015-06-16 14:51:24 -07:00
commit 5e597c5f0d
6 changed files with 3 additions and 548 deletions

View File

@ -3212,34 +3212,6 @@
}
]
},
{
"path": "/api/v1/redirect/nodes/{name}",
"description": "API at /api/v1 version v1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Node",
"nickname": "redirectNode",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/proxy/nodes/{name}/{path:*}",
"description": "API at /api/v1 version v1",
@ -5302,42 +5274,6 @@
}
]
},
{
"path": "/api/v1/redirect/namespaces/{namespaces}/pods/{name}",
"description": "API at /api/v1 version v1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Pod",
"nickname": "redirectPod",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "namespaces",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Pod",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/proxy/namespaces/{namespaces}/pods/{name}/{path:*}",
"description": "API at /api/v1 version v1",
@ -10187,42 +10123,6 @@
}
]
},
{
"path": "/api/v1/redirect/namespaces/{namespaces}/services/{name}",
"description": "API at /api/v1 version v1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Service",
"nickname": "redirectService",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "namespaces",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Service",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/proxy/namespaces/{namespaces}/services/{name}/{path:*}",
"description": "API at /api/v1 version v1",

View File

@ -3212,34 +3212,6 @@
}
]
},
{
"path": "/api/v1beta3/redirect/nodes/{name}",
"description": "API at /api/v1beta3 version v1beta3",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Node",
"nickname": "redirectNode",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta3/proxy/nodes/{name}/{path:*}",
"description": "API at /api/v1beta3 version v1beta3",
@ -5302,42 +5274,6 @@
}
]
},
{
"path": "/api/v1beta3/redirect/namespaces/{namespaces}/pods/{name}",
"description": "API at /api/v1beta3 version v1beta3",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Pod",
"nickname": "redirectPod",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "namespaces",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Pod",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta3/proxy/namespaces/{namespaces}/pods/{name}/{path:*}",
"description": "API at /api/v1beta3 version v1beta3",
@ -10187,42 +10123,6 @@
}
]
},
{
"path": "/api/v1beta3/redirect/namespaces/{namespaces}/services/{name}",
"description": "API at /api/v1beta3 version v1beta3",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "redirect GET request to Service",
"nickname": "redirectService",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "namespaces",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Service",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta3/proxy/namespaces/{namespaces}/services/{name}/{path:*}",
"description": "API at /api/v1beta3 version v1beta3",

View File

@ -1,7 +1,6 @@
# User Guide to Accessing the Cluster
* [Accessing the cluster API](#api)
* [Accessing services running on the cluster](#otherservices)
* [Requesting redirects](#redirect)
* [So many proxies](#somanyproxies)
## Accessing the cluster API<a name="api"></a>
@ -203,100 +202,7 @@ You may be able to put a apiserver proxy url into the address bar of a browser.
way that is unaware of the proxy path prefix.
## <a name="redirect"></a>Requesting redirects
Use a `redirect` request so that the server returns an HTTP redirect response and identifies the specific node and service that
can handle the request.
**Note**: Since the hostname or address that is returned is usually only accessible from inside the cluster,
sending `redirect` requests is useful only for code running inside the cluster. Also, keep in mind that any subsequent `redirect` requests to the same
server might return different results (because another node at that point in time can better serve the request).
**Tip**: Use a redirect request to reduce calls to the proxy server by first obtaining the address of a node on the
cluster and then using that returned address for all subsequent requests.
##### Example
To request a redirect and then verify the address that gets returned, let's run a query on `oban` (Google Compute Engine virtual machine). Note that `oban` is running in the same project and default network (Google Compute Engine) as the Kubernetes cluster.
To request a redirect for the Elasticsearch service, we can run the following `curl` command:
```
user@oban:~$ curl -L -k -u admin:4mty0Vl9nNFfwLJz https://104.197.5.247/api/v1/redirect/namespaces/default/services/elasticsearch-logging/
{
"status" : 200,
"name" : "Skin",
"cluster_name" : "kubernetes_logging",
"version" : {
"number" : "1.4.4",
"build_hash" : "c88f77ffc81301dfa9dfd81ca2232f09588bd512",
"build_timestamp" : "2015-02-19T13:05:36Z",
"build_snapshot" : false,
"lucene_version" : "4.10.3"
},
"tagline" : "You Know, for Search"
}
```
**Note**: We use the `-L` flag in the request so that `curl` follows the returned redirect address and retrieves the Elasticsearch service information.
If we examine the actual redirect header (instead run the same `curl` command with `-v`), we see that the request to `https://104.197.5.247/api/v1/redirect/namespaces/default/services/elasticsearch-logging/` is redirected to `http://10.244.2.7:9200`:
```
user@oban:~$ curl -v -k -u admin:4mty0Vl9nNFfwLJz https://104.197.5.247/api/v1/redirect/namespaces/default/services/elasticsearch-logging/
* About to connect() to 104.197.5.247 port 443 (#0)
* Trying 104.197.5.247...
* connected
* Connected to 104.197.5.247 (104.197.5.247) port 443 (#0)
* successfully set certificate verify locations:
* CAfile: none
CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using ECDHE-RSA-AES256-GCM-SHA384
* Server certificate:
* subject: CN=kubernetes-master
* start date: 2015-03-04 19:40:24 GMT
* expire date: 2025-03-01 19:40:24 GMT
* issuer: CN=104.197.5.247@1425498024
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Server auth using Basic with user 'admin'
> GET /api/v1/redirect/namespaces/default/services/elasticsearch-logging HTTP/1.1
> Authorization: Basic YWRtaW46M210eTBWbDluTkZmd0xKeg==
> User-Agent: curl/7.26.0
> Host: 104.197.5.247
> Accept: */*
>
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 307 Temporary Redirect
< Server: nginx/1.2.1
< Date: Thu, 05 Mar 2015 00:14:45 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 0
< Connection: keep-alive
< Location: http://10.244.2.7:9200
<
* Connection #0 to host 104.197.5.247 left intact
* Closing connection #0
* SSLv3, TLS alert, Client hello (1):
```
We can also run the `kubectl get pods` command to view a list of the pods on the cluster and verify that `http://10.244.2.7` is where the Elasticsearch service is running:
```
$ kubectl get pods
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS CREATED
elasticsearch-logging-controller-gziey 10.244.2.7 elasticsearch-logging kubernetes/elasticsearch:1.0 kubernetes-minion-hqhv.c.kubernetes-user2.internal/104.154.33.252 kubernetes.io/cluster-service=true,name=elasticsearch-logging Running 5 hours
kibana-logging-controller-ls6k1 10.244.1.9 kibana-logging kubernetes/kibana:1.1 kubernetes-minion-h5kt.c.kubernetes-user2.internal/146.148.80.37 kubernetes.io/cluster-service=true,name=kibana-logging Running 5 hours
kube-dns-oh43e 10.244.1.10 etcd quay.io/coreos/etcd:v2.0.3 kubernetes-minion-h5kt.c.kubernetes-user2.internal/146.148.80.37 k8s-app=kube-dns,kubernetes.io/cluster-service=true,name=kube-dns Running 5 hours
kube2sky kubernetes/kube2sky:1.0
skydns kubernetes/skydns:2014-12-23-001
monitoring-heapster-controller-fplln 10.244.0.4 heapster kubernetes/heapster:v0.8 kubernetes-minion-2il2.c.kubernetes-user2.internal/130.211.155.16 kubernetes.io/cluster-service=true,name=heapster,uses=monitoring-influxdb Running 5 hours
monitoring-influx-grafana-controller-0133o 10.244.3.4 influxdb kubernetes/heapster_influxdb:v0.3 kubernetes-minion-kmin.c.kubernetes-user2.internal/130.211.173.22 kubernetes.io/cluster-service=true,name=influxGrafana Running 5 hours
grafana kubernetes/heapster_grafana:v0.4
```
The redirect capabilities have been deprecated and removed. Please use a proxy (see below) instead.
##<a name="somanyproxies"></a>So Many Proxies
There are several different proxies you may encounter when using kubernetes:

View File

@ -62,7 +62,6 @@ func (a *APIInstaller) Install(proxyDialer func(network, addr string) (net.Conn,
// Create the WebService.
ws = a.newWebService()
redirectHandler := (&RedirectHandler{a.group.Storage, a.group.Codec, a.group.Context, a.info})
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info, proxyDialer})
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
@ -74,7 +73,7 @@ func (a *APIInstaller) Install(proxyDialer func(network, addr string) (net.Conn,
}
sort.Strings(paths)
for _, path := range paths {
if err := a.registerResourceHandlers(path, a.group.Storage[path], ws, redirectHandler, proxyHandler); err != nil {
if err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler); err != nil {
errors = append(errors, err)
}
}
@ -93,7 +92,7 @@ func (a *APIInstaller) newWebService() *restful.WebService {
return ws
}
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, redirectHandler, proxyHandler http.Handler) error {
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) error {
admit := a.group.Admit
context := a.group.Context
@ -277,7 +276,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter)
@ -316,7 +314,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter)
@ -360,7 +357,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
actions = appendIf(actions, action{"REDIRECT", "redirect/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector)
actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter)
@ -568,20 +564,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
}
addParams(route, action.Params)
ws.Route(route)
case "REDIRECT": // Get the redirect URL for a resource.
doc := "redirect GET request to " + kind
if hasSubresource {
doc = "redirect GET request to " + subresource + " of " + kind
}
route := ws.GET(action.Path).To(routeFunction(redirectHandler)).
Filter(m).
Doc(doc).
Operation("redirect" + kind + strings.Title(subresource)).
Produces("*/*").
Consumes("*/*").
Writes("string")
addParams(route, action.Params)
ws.Route(route)
case "PROXY": // Proxy requests to a resource.
// Accept all methods as per https://github.com/GoogleCloudPlatform/kubernetes/issues/3996
addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, kind, resource, subresource, hasSubresource, action.Params)

View File

@ -1,104 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"net/http"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
type RedirectHandler struct {
storage map[string]rest.Storage
codec runtime.Codec
context api.RequestContextMapper
apiRequestInfoResolver *APIRequestInfoResolver
}
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var verb string
var apiResource string
var httpCode int
reqStart := time.Now()
defer monitor(&verb, &apiResource, util.GetClient(req), &httpCode, reqStart)
requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
httpCode = http.StatusNotFound
return
}
verb = requestInfo.Verb
resource, parts := requestInfo.Resource, requestInfo.Parts
ctx, ok := r.context.Get(req)
if !ok {
ctx = api.NewContext()
}
ctx = api.WithNamespace(ctx, requestInfo.Namespace)
// redirection requires /resource/resourceName path parts
if len(parts) != 2 || req.Method != "GET" {
notFound(w, req)
httpCode = http.StatusNotFound
return
}
id := parts[1]
storage, ok := r.storage[resource]
if !ok {
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
notFound(w, req)
httpCode = http.StatusNotFound
return
}
apiResource = resource
redirector, ok := storage.(rest.Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
httpCode = errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w)
return
}
location, _, err := redirector.ResourceLocation(ctx, id)
if err != nil {
status := errToAPIStatus(err)
writeJSON(status.Code, r.codec, status, w, true)
httpCode = status.Code
return
}
if location == nil {
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
notFound(w, req)
httpCode = http.StatusNotFound
return
}
// Default to http
if location.Scheme == "" {
location.Scheme = "http"
}
w.Header().Set("Location", location.String())
w.WriteHeader(http.StatusTemporaryRedirect)
httpCode = http.StatusTemporaryRedirect
}

View File

@ -1,129 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
)
func TestRedirect(t *testing.T) {
simpleStorage := &SimpleRESTStorage{
errors: map[string]error{},
expectedResourceNamespace: "default",
}
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
dontFollow := errors.New("don't follow")
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return dontFollow
},
}
table := []struct {
id string
err error
code int
}{
{"cozy", nil, http.StatusTemporaryRedirect},
{"horse", errors.New("no such id"), http.StatusInternalServerError},
}
for _, item := range table {
simpleStorage.errors["resourceLocation"] = item.err
simpleStorage.resourceLocation = &url.URL{Host: item.id}
resp, err := client.Get(server.URL + "/api/version/redirect/foo/" + item.id)
if resp == nil {
t.Fatalf("Unexpected nil resp")
}
resp.Body.Close()
if e, a := item.code, resp.StatusCode; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if e, a := item.id, simpleStorage.requestedResourceLocationID; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if item.err != nil {
continue
}
if err == nil || err.(*url.Error).Err != dontFollow {
t.Errorf("Unexpected err %#v", err)
}
if e, a := "http://"+item.id, resp.Header.Get("Location"); e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
}
func TestRedirectWithNamespaces(t *testing.T) {
simpleStorage := &SimpleRESTStorage{
errors: map[string]error{},
expectedResourceNamespace: "other",
}
handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
server := httptest.NewServer(handler)
defer server.Close()
dontFollow := errors.New("don't follow")
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return dontFollow
},
}
table := []struct {
id string
err error
code int
}{
{"cozy", nil, http.StatusTemporaryRedirect},
{"horse", errors.New("no such id"), http.StatusInternalServerError},
}
for _, item := range table {
simpleStorage.errors["resourceLocation"] = item.err
simpleStorage.resourceLocation = &url.URL{Host: item.id}
resp, err := client.Get(server.URL + "/api/version2/redirect/namespaces/other/foo/" + item.id)
if resp == nil {
t.Fatalf("Unexpected nil resp")
}
resp.Body.Close()
if e, a := item.code, resp.StatusCode; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if e, a := item.id, simpleStorage.requestedResourceLocationID; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if item.err != nil {
continue
}
if err == nil || err.(*url.Error).Err != dontFollow {
t.Errorf("Unexpected err %#v", err)
}
if e, a := "http://"+item.id, resp.Header.Get("Location"); e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
}