18790: Error out instead of retrying if server is too old.
[arvados.git] / lib / controller / localdb / container_gateway.go
index 8a70dc8e81264ceec663cf2d90d901b5140c4211..74c00da8b1251fbd8d612d0e46ae6a7144e908dd 100644 (file)
@@ -21,6 +21,7 @@ import (
        "net/http"
        "net/http/httputil"
        "net/url"
+       "os"
        "strings"
 
        "git.arvados.org/arvados.git/lib/controller/rpc"
@@ -56,6 +57,13 @@ var (
 func (conn *Conn) ContainerLog(ctx context.Context, opts arvados.ContainerLogOptions) (http.Handler, error) {
        ctr, err := conn.railsProxy.ContainerGet(ctx, arvados.GetOptions{UUID: opts.UUID, Select: []string{"uuid", "state", "gateway_address", "log"}})
        if err != nil {
+               if se := httpserver.HTTPStatusError(nil); errors.As(err, &se) && se.HTTPStatus() == http.StatusUnauthorized {
+                       // Hint to WebDAV client that we accept HTTP basic auth.
+                       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+                               w.Header().Set("Www-Authenticate", "Basic realm=\"collections\"")
+                               w.WriteHeader(http.StatusUnauthorized)
+                       }), nil
+               }
                return nil, err
        }
        if ctr.GatewayAddress == "" ||
@@ -168,7 +176,7 @@ func (conn *Conn) serveContainerLogViaKeepWeb(opts arvados.ContainerLogOptions,
        myHostname := u.Hostname()
        var webdavBase arvados.URL
        var ok bool
-       for webdavBase = range conn.cluster.Services.WebDAVDownload.InternalURLs {
+       for webdavBase = range conn.cluster.Services.WebDAV.InternalURLs {
                ok = true
                u := url.URL(webdavBase)
                if h := u.Hostname(); h == "127.0.0.1" || h == "0.0.0.0" || h == "::1" || h == myHostname {
@@ -184,15 +192,11 @@ func (conn *Conn) serveContainerLogViaKeepWeb(opts arvados.ContainerLogOptions,
        }
        proxy := &httputil.ReverseProxy{
                Director: func(r *http.Request) {
-                       r.URL = &url.URL{
-                               Scheme: webdavBase.Scheme,
-                               Host:   webdavBase.Host,
-                               Path:   "/by_id/" + url.PathEscape(ctr.Log) + opts.Path,
-                       }
-                       // Our outgoing Host header must match
-                       // WebDAVDownload.ExternalURL, otherwise
-                       // keep-web does not accept an auth token.
-                       r.Host = conn.cluster.Services.WebDAVDownload.ExternalURL.Host
+                       r.URL.Scheme = webdavBase.Scheme
+                       r.URL.Host = webdavBase.Host
+                       // Outgoing Host header specifies the
+                       // collection ID.
+                       r.Host = strings.Replace(ctr.Log, "+", "-", -1) + ".internal"
                        // We already checked permission on the
                        // container, so we can use a root token here
                        // instead of counting on the "access to log
@@ -200,6 +204,12 @@ func (conn *Conn) serveContainerLogViaKeepWeb(opts arvados.ContainerLogOptions,
                        // permission check, which can be racy when a
                        // request gets retried with a new container.
                        r.Header.Set("Authorization", "Bearer "+conn.cluster.SystemRootToken)
+                       // We can't change r.URL.Path without
+                       // confusing WebDAV (request body and response
+                       // headers refer to the same paths) so we tell
+                       // keep-web to map the log collection onto the
+                       // containers/X/log/ namespace.
+                       r.Header.Set("X-Webdav-Prefix", "/arvados/v1/containers/"+ctr.UUID+"/log")
                },
        }
        if conn.cluster.TLS.Insecure {
@@ -222,7 +232,7 @@ func (conn *Conn) serveEmptyDir(path string, w http.ResponseWriter, r *http.Requ
                FileSystem: webdav.NewMemFS(),
                LockSystem: webdavfs.NoLockSystem,
                Logger: func(r *http.Request, err error) {
-                       if err != nil {
+                       if err != nil && !os.IsNotExist(err) {
                                ctxlog.FromContext(r.Context()).WithError(err).Info("webdav error on empty collection fs")
                        }
                },
@@ -429,7 +439,7 @@ func (conn *Conn) findGateway(ctx context.Context, ctr arvados.Container, noForw
                return func() (net.Conn, string, string, error) {
                        rawconn, err := (&net.Dialer{}).DialContext(ctx, "tcp", ctr.GatewayAddress)
                        if err != nil {
-                               err = httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
+                               return nil, "", "", httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
                        }
                        return conn.dialGatewayTLS(ctx, ctr, rawconn)
                }, nil, nil
@@ -451,7 +461,7 @@ func (conn *Conn) findGateway(ctx context.Context, ctr arvados.Container, noForw
                return func() (net.Conn, string, string, error) {
                        rawconn, err := tunnel.Open()
                        if err != nil {
-                               err = httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
+                               return nil, "", "", httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
                        }
                        return conn.dialGatewayTLS(ctx, ctr, rawconn)
                }, nil, nil