Change:
The apiserver proxy rewrites links to the form "/proxy/ns/default/...": But this form stopped working recently, seemingly around The "/proxy/ns/default/..." form always returns "404: Page Not Found", similar to the problem seen in If you remove "/ns/default" from the URL, it works.
/cc
I can investigate this as well.
I see the issue, sending a PR.
The proxy path needs to be built with knowledge of the apiversion of the incoming proxy request, and it needs to be updated to use the knowledge from the restmapper. This is a little trickier than I have time for right now, but it should definitely not be hardcode appending the /ns form. Not clear why this is not also caught by our test case. I can try and look at this more in the AM unless someone else wants to take a look in interim. /cc
Let me steal this one from you, I think I can fix it.
Have at it. /cc me on your PR
Sorry, I see you've already got a PR out!
No problem. I would be happy if you finished my PR relative to the commit I started. It will be a few days before I can go back to looking at this issue. I think v1beta1/v1beta2 proxy urls in go-restful need to always but the namespace scope in the path as the ultimate fix. I do want the proxy to have access to the restmapper per my PR. ----- Original Message ----- From: "Daniel Smith" To: "GoogleCloudPlatform/kubernetes" Cc: "Derek Carr" Sent: Friday, February 13, 2015 4:56:19 PM Subject: Re: [kubernetes] apiserver proxy stopped accepting namespaces () Sorry, I see you've already got a PR out! Reply to this email directly or view it on GitHub:
Any updates ?
This is breaking monitoring since it requires the proxy. Bumping up the priority to P0.
Sorry for dropping this. Working on a fix.

Discussion:
import ( "fmt" "net/http" <ins> "path" </ins> "regexp" "runtime/debug" "strings"

Kind string // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in. Name string <del> // Parts are the path parts for the request relative to /{resource}/{name} </del> <ins> // Parts are the path parts for the request, always starting with /{resource}/{name} </ins> Parts []string <ins> // Raw is the unparsed form of everything other than parts. // Raw + Parts = complete URL path Raw []string } // URLPath returns the URL path for this request, including /{resource}/{name} if present but nothing // following that. func (info APIRequestInfo) URLPath() string { p := info.Parts if n := len(p); n > 2 { // Only take resource and name p = p[:2] } return path.Join("/", path.Join(info.Raw...), path.Join(p...)) </ins> } type APIRequestInfoResolver struct {

// /api/{version}/* // /api/{version}/* func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) { <del> requestInfo := APIRequestInfo{} </del> <ins> requestInfo := APIRequestInfo{ Raw: splitPath(req.URL.Path), } </ins> <del> currentParts := splitPath(req.URL.Path) </del> <ins> currentParts := requestInfo.Raw </ins> if len(currentParts) < 1 { return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path") }

// parsing successful, so we now know the proper value for .Parts requestInfo.Parts = currentParts <ins> // Raw should have everything not in Parts requestInfo.Raw = requestInfo.Raw[:len(requestInfo.Raw)-len(currentParts)] </ins> // if there's another part remaining after the kind, then that's the resource name if len(requestInfo.Parts) >= 2 {

"net/http" "net/http/httptest" "reflect" <ins> "strings" </ins> "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"

if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) { t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts) } <ins> if e, a := strings.Split(successCase.url, "?")[0], apiRequestInfo.URLPath(); e != a { t.Errorf("Expected %v, got %v", e, a) } </ins> } errorCases := map[string]string{

// TODO convert this entire proxy to an UpgradeAwareProxy similar to // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go. // That proxy needs to be modified to support multiple backends, not just 1. <ins> if r.tryUpgrade(w, req, newReq, destURL) { return } proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: destURL.Host}) proxy.Transport = &proxyTransport{ proxyScheme:      req.URL.Scheme, proxyHost:        req.URL.Host, proxyPathPrepend: requestInfo.URLPath(), } proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) } // tryUpgrade returns true if the request was handled. func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Request, destURL *url.URL) bool { </ins> connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection)) <del> if strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) && len(req.Header.Get(httpstream.HeaderUpgrade)) > 0 { //TODO support TLS? Doesn't look like proxyTransport does anything special ... dialAddr := netutil.CanonicalAddr(destURL) backendConn, err := net.Dial("tcp", dialAddr) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return } defer backendConn.Close() </del> <ins> if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || len(req.Header.Get(httpstream.HeaderUpgrade)) == 0 { return false } //TODO support TLS? Doesn't look like proxyTransport does anything special ... dialAddr := netutil.CanonicalAddr(destURL) backendConn, err := net.Dial("tcp", dialAddr) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return true } defer backendConn.Close() </ins> <del> // TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn // when copying between the client and the backend? Docker doesn't when they // hijack, just for reference... requestHijackedConn, _, err := w.(http.Hijacker).Hijack() if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return } defer requestHijackedConn.Close() </del> <ins> // TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn // when copying between the client and the backend? Docker doesn't when they // hijack, just for reference... requestHijackedConn, _, err := w.(http.Hijacker).Hijack() if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return true } defer requestHijackedConn.Close() </ins> <del> if err = newReq.Write(backendConn); err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return </del> <ins> if err = newReq.Write(backendConn); err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return true } done := make(chan struct{}, 2) go func() { _, err := io.Copy(backendConn, requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { glog.Errorf("Error proxying data from client to backend: %v", err) </ins> } <ins> done <- struct{}{} }() </ins> <del> done := make(chan struct{}, 2) go func() { _, err := io.Copy(backendConn, requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { glog.Errorf("Error proxying data from client to backend: %v", err) } done <- struct{}{} }() go func() { _, err := io.Copy(requestHijackedConn, backendConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { glog.Errorf("Error proxying data from backend to client: %v", err) } done <- struct{}{} }() <-done } else { proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: destURL.Host}) proxy.Transport = &proxyTransport{ proxyScheme:      req.URL.Scheme, proxyHost:        req.URL.Host, proxyPathPrepend: path.Join(r.prefix, "ns", namespace, resource, id), </del> <ins> go func() { _, err := io.Copy(requestHijackedConn, backendConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { glog.Errorf("Error proxying data from backend to client: %v", err) </ins> } <del> proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) } </del> <ins> done <- struct{}{} }() <-done return true </ins> } type proxyTransport struct {