Merge branch '5824-keep-web' into 5824-keep-web-workbench
authorTom Clegg <tom@curoverse.com>
Fri, 30 Oct 2015 18:30:13 +0000 (14:30 -0400)
committerTom Clegg <tom@curoverse.com>
Fri, 30 Oct 2015 18:30:13 +0000 (14:30 -0400)
Conflicts:
sdk/python/tests/run_test_server.py
services/keepproxy/keepproxy_test.go

1  2 
sdk/python/tests/nginx.conf
sdk/python/tests/run_test_server.py
services/keepproxy/keepproxy.go
services/keepproxy/keepproxy_test.go

index 885f84e7ef4520d0e5df81970c15d9e81bff12a9,d8a207f2cf2ce506d9406a646272e92c51c201e6..b14c674523f067469cbdfebd96dd035597b4bb44
@@@ -3,7 -3,7 +3,7 @@@ error_log stderr info;          # Yes, 
  events {
  }
  http {
-   access_log /dev/stderr combined;
+   access_log {{ACCESSLOG}} combined;
    upstream arv-git-http {
      server localhost:{{GITPORT}};
    }
        proxy_pass http://keepproxy;
      }
    }
 +  upstream keep-web {
 +    server localhost:{{KEEPWEBPORT}};
 +  }
 +  server {
 +    listen *:{{KEEPWEBSSLPORT}} ssl default_server;
 +    server_name ~^(?<request_host>.*)$;
 +    ssl_certificate {{SSLCERT}};
 +    ssl_certificate_key {{SSLKEY}};
 +    location  / {
 +      proxy_pass http://keep-web;
 +      proxy_set_header Host $request_host:{{KEEPWEBPORT}};
 +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 +    }
 +  }
  }
index 591b500cfe45b1a43ce0057e47cee5866b1bf42d,d325b4eb6ecb086d15effa34bc26db3e95c9ad15..f8f8b18d7637bed76966dc637a622af3dbcb9020
@@@ -3,6 -3,7 +3,7 @@@
  from __future__ import print_function
  import argparse
  import atexit
+ import errno
  import httplib2
  import os
  import pipes
@@@ -54,9 -55,7 +55,7 @@@ def find_server_pid(PID_PATH, wait=10)
              with open(PID_PATH, 'r') as f:
                  server_pid = int(f.read())
              good_pid = (os.kill(server_pid, 0) is None)
-         except IOError:
-             good_pid = False
-         except OSError:
+         except EnvironmentError:
              good_pid = False
          now = time.time()
  
@@@ -91,9 -90,7 +90,7 @@@ def kill_server_pid(pidfile, wait=10, p
              os.getpgid(server_pid)
              time.sleep(0.1)
              now = time.time()
-     except IOError:
-         pass
-     except OSError:
+     except EnvironmentError:
          pass
  
  def find_available_port():
@@@ -345,7 -342,7 +342,7 @@@ def run_keep(blob_signing_key=None, enf
          token=os.environ['ARVADOS_API_TOKEN'],
          insecure=True)
  
 -    for d in api.keep_services().list().execute()['items']:
 +    for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
          api.keep_services().delete(uuid=d['uuid']).execute()
      for d in api.keep_disks().list().execute()['items']:
          api.keep_disks().delete(uuid=d['uuid']).execute()
              'keep_disk': {'keep_service_uuid': svc['uuid'] }
          }).execute()
  
 +    # If keepproxy is running, send SIGHUP to make it discover the new
 +    # keepstore services.
 +    proxypidfile = _pidfile('keepproxy')
 +    if os.path.exists(proxypidfile):
 +        os.kill(int(open(proxypidfile).read()), signal.SIGHUP)
 +
  def _stop_keep(n):
      kill_server_pid(_pidfile('keep{}'.format(n)), 0)
      if os.path.exists("{}/keep{}.volume".format(TEST_TMPDIR, n)):
@@@ -444,41 -435,17 +441,42 @@@ def stop_arv_git_httpd()
          return
      kill_server_pid(_pidfile('arv-git-httpd'), wait=0)
  
 +def run_keep_web():
 +    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
 +        return
 +    stop_keep_web()
 +
 +    keepwebport = find_available_port()
 +    env = os.environ.copy()
 +    env.pop('ARVADOS_API_TOKEN', None)
 +    keepweb = subprocess.Popen(
 +        ['keep-web',
 +         '-attachment-only-host=localhost:'+str(keepwebport),
 +         '-address=:'+str(keepwebport)],
 +        env=env, stdin=open('/dev/null'), stdout=sys.stderr)
 +    with open(_pidfile('keep-web'), 'w') as f:
 +        f.write(str(keepweb.pid))
 +    _setport('keep-web', keepwebport)
 +    _wait_until_port_listens(keepwebport)
 +
 +def stop_keep_web():
 +    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
 +        return
 +    kill_server_pid(_pidfile('keep-web'), wait=0)
 +
  def run_nginx():
      if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
          return
      nginxconf = {}
 +    nginxconf['KEEPWEBPORT'] = _getport('keep-web')
 +    nginxconf['KEEPWEBSSLPORT'] = find_available_port()
      nginxconf['KEEPPROXYPORT'] = _getport('keepproxy')
      nginxconf['KEEPPROXYSSLPORT'] = find_available_port()
      nginxconf['GITPORT'] = _getport('arv-git-httpd')
      nginxconf['GITSSLPORT'] = find_available_port()
      nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
      nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
+     nginxconf['ACCESSLOG'] = os.path.join(TEST_TMPDIR, 'nginx_access_log.fifo')
  
      conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
      conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
  
      env = os.environ.copy()
      env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
+     try:
+         os.remove(nginxconf['ACCESSLOG'])
+     except OSError as error:
+         if error.errno != errno.ENOENT:
+             raise
+     os.mkfifo(nginxconf['ACCESSLOG'], 0700)
      nginx = subprocess.Popen(
          ['nginx',
           '-g', 'error_log stderr info;',
           '-g', 'pid '+_pidfile('nginx')+';',
           '-c', conffile],
          env=env, stdin=open('/dev/null'), stdout=sys.stderr)
+     cat_access = subprocess.Popen(
+         ['cat', nginxconf['ACCESSLOG']],
+         stdout=sys.stderr)
 +    _setport('keep-web-ssl', nginxconf['KEEPWEBSSLPORT'])
      _setport('keepproxy-ssl', nginxconf['KEEPPROXYSSLPORT'])
      _setport('arv-git-httpd-ssl', nginxconf['GITSSLPORT'])
  
@@@ -578,7 -555,6 +587,7 @@@ class TestCaseWithServers(unittest.Test
      MAIN_SERVER = None
      KEEP_SERVER = None
      KEEP_PROXY_SERVER = None
 +    KEEP_WEB_SERVER = None
  
      @staticmethod
      def _restore_dict(src, dest):
          for server_kwargs, start_func, stop_func in (
                  (cls.MAIN_SERVER, run, reset),
                  (cls.KEEP_SERVER, run_keep, stop_keep),
 -                (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
 +                (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
 +                (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
              if server_kwargs is not None:
                  start_func(**server_kwargs)
                  cls._cleanup_funcs.append(stop_func)
@@@ -624,7 -599,6 +633,7 @@@ if __name__ == "__main__"
          'start', 'stop',
          'start_keep', 'stop_keep',
          'start_keep_proxy', 'stop_keep_proxy',
 +        'start_keep-web', 'stop_keep-web',
          'start_arv-git-httpd', 'stop_arv-git-httpd',
          'start_nginx', 'stop_nginx',
      ]
          run_arv_git_httpd()
      elif args.action == 'stop_arv-git-httpd':
          stop_arv_git_httpd()
 +    elif args.action == 'start_keep-web':
 +        run_keep_web()
 +    elif args.action == 'stop_keep-web':
 +        stop_keep_web()
      elif args.action == 'start_nginx':
          run_nginx()
      elif args.action == 'stop_nginx':
index 8cfaa9045808c50d7a8bca9129a3db658fcbe506,1a1189658d7ba1473aa33036cce60276932cb9b8..bad0d22bf1a81868799d8a437860f941d6fbe770
@@@ -22,8 -22,8 +22,8 @@@ import 
  )
  
  // Default TCP address on which to listen for requests.
 -// Initialized by the -listen flag.
 -const DEFAULT_ADDR = ":25107"
 +// Override with -listen.
 +const DefaultAddr = ":25107"
  
  var listener net.Listener
  
@@@ -42,7 -42,7 +42,7 @@@ func main() 
        flagset.StringVar(
                &listen,
                "listen",
 -              DEFAULT_ADDR,
 +              DefaultAddr,
                "Interface on which to listen for requests, in the format "+
                        "ipaddr:port. e.g. -listen=10.0.1.24:8000. Use -listen=:port "+
                        "to listen on all network interfaces.")
  
        flagset.Parse(os.Args[1:])
  
 -      arv, err := arvadosclient.MakeArvadosClient()
 -      if err != nil {
 -              log.Fatalf("Error setting up arvados client %s", err.Error())
 -      }
 -
 -      kc, err := keepclient.MakeKeepClient(&arv)
 -      if err != nil {
 -              log.Fatalf("Error setting up keep client %s", err.Error())
 -      }
 -
        if pidfile != "" {
                f, err := os.Create(pidfile)
                if err != nil {
                defer os.Remove(pidfile)
        }
  
 +      arv, err := arvadosclient.MakeArvadosClient()
 +      if err != nil {
 +              log.Fatalf("setting up arvados client: %v", err)
 +      }
 +      kc, err := keepclient.MakeKeepClient(&arv)
 +      if err != nil {
 +              log.Fatalf("setting up keep client: %v", err)
 +      }
        kc.Want_replicas = default_replicas
 -
        kc.Client.Timeout = time.Duration(timeout) * time.Second
 +      go RefreshServicesList(kc, 5*time.Minute, 3*time.Second)
  
        listener, err = net.Listen("tcp", listen)
        if err != nil {
                log.Fatalf("Could not listen on %v", listen)
        }
 -
 -      go RefreshServicesList(kc)
 +      log.Printf("Arvados Keep proxy started listening on %v", listener.Addr())
  
        // Shut down the server gracefully (by closing the listener)
        // if SIGTERM is received.
        signal.Notify(term, syscall.SIGTERM)
        signal.Notify(term, syscall.SIGINT)
  
 -      log.Printf("Arvados Keep proxy started listening on %v", listener.Addr())
 -
 -      // Start listening for requests.
 +      // Start serving requests.
        http.Serve(listener, MakeRESTRouter(!no_get, !no_put, kc))
  
        log.Println("shutting down")
@@@ -130,39 -135,27 +130,39 @@@ type ApiTokenCache struct 
        expireTime int64
  }
  
 -// Refresh the keep service list every five minutes.
 -func RefreshServicesList(kc *keepclient.KeepClient) {
 +// Refresh the keep service list on SIGHUP; when the given interval
 +// has elapsed since the last refresh; and (if the last refresh
 +// failed) the given errInterval has elapsed.
 +func RefreshServicesList(kc *keepclient.KeepClient, interval, errInterval time.Duration) {
        var previousRoots = []map[string]string{}
 -      var delay time.Duration = 0
 +
 +      timer := time.NewTimer(interval)
 +      gotHUP := make(chan os.Signal, 1)
 +      signal.Notify(gotHUP, syscall.SIGHUP)
 +
        for {
 -              time.Sleep(delay * time.Second)
 -              delay = 300
 +              select {
 +              case <-gotHUP:
 +              case <-timer.C:
 +              }
 +              timer.Reset(interval)
 +
                if err := kc.DiscoverKeepServers(); err != nil {
 -                      log.Println("Error retrieving services list:", err)
 -                      delay = 3
 +                      log.Println("Error retrieving services list: %v (retrying in %v)", err, errInterval)
 +                      timer.Reset(errInterval)
                        continue
                }
                newRoots := []map[string]string{kc.LocalRoots(), kc.GatewayRoots()}
 +
                if !reflect.DeepEqual(previousRoots, newRoots) {
                        log.Printf("Updated services list: locals %v gateways %v", newRoots[0], newRoots[1])
 +                      previousRoots = newRoots
                }
 +
                if len(newRoots[0]) == 0 {
 -                      log.Print("WARNING: No local services. Retrying in 3 seconds.")
 -                      delay = 3
 +                      log.Printf("WARNING: No local services (retrying in %v)", errInterval)
 +                      timer.Reset(errInterval)
                }
 -              previousRoots = newRoots
        }
  }
  
@@@ -198,13 -191,17 +198,13 @@@ func (this *ApiTokenCache) RecallToken(
  }
  
  func GetRemoteAddress(req *http.Request) string {
 -      if realip := req.Header.Get("X-Real-IP"); realip != "" {
 -              if forwarded := req.Header.Get("X-Forwarded-For"); forwarded != realip {
 -                      return fmt.Sprintf("%s (X-Forwarded-For %s)", realip, forwarded)
 -              } else {
 -                      return realip
 -              }
 +      if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
 +              return xff + "," + req.RemoteAddr
        }
        return req.RemoteAddr
  }
  
- func CheckAuthorizationHeader(kc keepclient.KeepClient, cache *ApiTokenCache, req *http.Request) (pass bool, tok string) {
+ func CheckAuthorizationHeader(kc *keepclient.KeepClient, cache *ApiTokenCache, req *http.Request) (pass bool, tok string) {
        var auth string
        if auth = req.Header.Get("Authorization"); auth == "" {
                return false, ""
@@@ -334,7 -331,7 +334,7 @@@ func (this GetBlockHandler) ServeHTTP(r
  
        var pass bool
        var tok string
-       if pass, tok = CheckAuthorizationHeader(kc, this.ApiTokenCache, req); !pass {
+       if pass, tok = CheckAuthorizationHeader(&kc, this.ApiTokenCache, req); !pass {
                status, err = http.StatusForbidden, BadAuthorizationHeader
                return
        }
                log.Println("Warning:", GetRemoteAddress(req), req.Method, proxiedURI, "Content-Length not provided")
        }
  
-       switch err {
+       switch respErr := err.(type) {
        case nil:
                status = http.StatusOK
                resp.Header().Set("Content-Length", fmt.Sprint(expectLength))
                                err = ContentLengthMismatch
                        }
                }
-       case keepclient.BlockNotFound:
-               status = http.StatusNotFound
+       case keepclient.Error:
+               if respErr == keepclient.BlockNotFound {
+                       status = http.StatusNotFound
+               } else if respErr.Temporary() {
+                       status = http.StatusBadGateway
+               } else {
+                       status = 422
+               }
        default:
-               status = http.StatusBadGateway
+               status = http.StatusInternalServerError
        }
  }
  
@@@ -435,7 -438,7 +441,7 @@@ func (this PutBlockHandler) ServeHTTP(r
  
        var pass bool
        var tok string
-       if pass, tok = CheckAuthorizationHeader(kc, this.ApiTokenCache, req); !pass {
+       if pass, tok = CheckAuthorizationHeader(&kc, this.ApiTokenCache, req); !pass {
                err = BadAuthorizationHeader
                status = http.StatusForbidden
                return
@@@ -518,7 -521,7 +524,7 @@@ func (handler IndexHandler) ServeHTTP(r
  
        kc := *handler.KeepClient
  
-       ok, token := CheckAuthorizationHeader(kc, handler.ApiTokenCache, req)
+       ok, token := CheckAuthorizationHeader(&kc, handler.ApiTokenCache, req)
        if !ok {
                status, err = http.StatusForbidden, BadAuthorizationHeader
                return
index f350e0b6570243407299a104b177c727c86cf7a5,2c75ec1616e3b404a9547b6e2f969ce9fc1fe9f2..e4f09b4e74a071b62797da22c3649ddd1f423938
@@@ -2,19 -2,21 +2,19 @@@ package mai
  
  import (
        "crypto/md5"
 -      "crypto/tls"
        "fmt"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
 -      . "gopkg.in/check.v1"
 -      "io"
        "io/ioutil"
        "log"
        "net/http"
 -      "net/url"
        "os"
        "strings"
        "testing"
        "time"
 +
 +      . "gopkg.in/check.v1"
  )
  
  // Gocheck boilerplate
@@@ -28,8 -30,12 +28,14 @@@ var _ = Suite(&ServerRequiredSuite{}
  // Tests that require the Keep server running
  type ServerRequiredSuite struct{}
  
+ // Gocheck boilerplate
+ var _ = Suite(&NoKeepServerSuite{})
+ // Test with no keepserver to simulate errors
+ type NoKeepServerSuite struct{}
 +var TestProxyUUID = "zzzzz-bi6l4-lrixqc4fxofbmzz"
 +
  // Wait (up to 1 second) for keepproxy to listen on a port. This
  // avoids a race condition where we hit a "connection refused" error
  // because we start testing the proxy too soon.
@@@ -53,7 -59,7 +59,7 @@@ func closeListener() 
  
  func (s *ServerRequiredSuite) SetUpSuite(c *C) {
        arvadostest.StartAPI()
-       arvadostest.StartKeep()
+       arvadostest.StartKeep(2, false)
  }
  
  func (s *ServerRequiredSuite) SetUpTest(c *C) {
  }
  
  func (s *ServerRequiredSuite) TearDownSuite(c *C) {
-       arvadostest.StopKeep()
+       arvadostest.StopKeep(2)
+       arvadostest.StopAPI()
+ }
+ func (s *NoKeepServerSuite) SetUpSuite(c *C) {
+       arvadostest.StartAPI()
+ }
+ func (s *NoKeepServerSuite) SetUpTest(c *C) {
+       arvadostest.ResetEnv()
+ }
+ func (s *NoKeepServerSuite) TearDownSuite(c *C) {
        arvadostest.StopAPI()
  }
  
 -func setupProxyService() {
 -
 -      client := &http.Client{Transport: &http.Transport{
 -              TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
 -
 -      var req *http.Request
 -      var err error
 -      if req, err = http.NewRequest("POST", fmt.Sprintf("https://%s/arvados/v1/keep_services", os.Getenv("ARVADOS_API_HOST")), nil); err != nil {
 -              panic(err.Error())
 -      }
 -      req.Header.Add("Authorization", fmt.Sprintf("OAuth2 %s", os.Getenv("ARVADOS_API_TOKEN")))
 -
 -      reader, writer := io.Pipe()
 -
 -      req.Body = reader
 -
 -      go func() {
 -              data := url.Values{}
 -              data.Set("keep_service", `{
 -  "service_host": "localhost",
 -  "service_port": 29950,
 -  "service_ssl_flag": false,
 -  "service_type": "proxy"
 -}`)
 -
 -              writer.Write([]byte(data.Encode()))
 -              writer.Close()
 -      }()
 -
 -      var resp *http.Response
 -      if resp, err = client.Do(req); err != nil {
 -              panic(err.Error())
 -      }
 -      if resp.StatusCode != 200 {
 -              panic(resp.Status)
 -      }
 -}
 +func runProxy(c *C, args []string, bogusClientToken bool) *keepclient.KeepClient {
 +      args = append([]string{"keepproxy"}, args...)
 +      os.Args = append(args, "-listen=:0")
 +      listener = nil
 +      go main()
 +      waitForListener()
  
 -func runProxy(c *C, args []string, port int, bogusClientToken bool) *keepclient.KeepClient {
 -      if bogusClientToken {
 -              os.Setenv("ARVADOS_API_TOKEN", "bogus-token")
 -      }
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, Equals, nil)
 -      kc := keepclient.KeepClient{
 -              Arvados:       &arv,
 -              Want_replicas: 2,
 -              Using_proxy:   true,
 -              Client:        &http.Client{},
 -      }
 -      locals := map[string]string{
 -              "proxy": fmt.Sprintf("http://localhost:%v", port),
 -      }
 -      writableLocals := map[string]string{
 -              "proxy": fmt.Sprintf("http://localhost:%v", port),
 -      }
 -      kc.SetServiceRoots(locals, writableLocals, nil)
 -      c.Check(kc.Using_proxy, Equals, true)
 -      c.Check(len(kc.LocalRoots()), Equals, 1)
 -      for _, root := range kc.LocalRoots() {
 -              c.Check(root, Equals, fmt.Sprintf("http://localhost:%v", port))
 -      }
 -      log.Print("keepclient created")
        if bogusClientToken {
 -              arvadostest.ResetEnv()
 +              arv.ApiToken = "bogus-token"
        }
 -
 -      {
 -              os.Args = append(args, fmt.Sprintf("-listen=:%v", port))
 -              listener = nil
 -              go main()
 +      kc := keepclient.New(&arv)
 +      sr := map[string]string{
 +              TestProxyUUID: "http://" + listener.Addr().String(),
        }
 +      kc.SetServiceRoots(sr, sr, sr)
 +      kc.Arvados.External = true
 +      kc.Using_proxy = true
  
-       return kc
+       return &kc
  }
  
  func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
 -      log.Print("TestPutAndGet start")
 -
 -      os.Args = []string{"keepproxy", "-listen=:29950"}
 -      listener = nil
 -      go main()
 -      time.Sleep(100 * time.Millisecond)
 -
 -      setupProxyService()
 -
 -      os.Setenv("ARVADOS_EXTERNAL_CLIENT", "true")
 -      arv, err := arvadosclient.MakeArvadosClient()
 -      c.Assert(err, Equals, nil)
 -      kc, err := keepclient.MakeKeepClient(&arv)
 -      c.Assert(err, Equals, nil)
 -      c.Check(kc.Arvados.External, Equals, true)
 -      c.Check(kc.Using_proxy, Equals, true)
 -      c.Check(len(kc.LocalRoots()), Equals, 1)
 -      for _, root := range kc.LocalRoots() {
 -              c.Check(root, Equals, "http://localhost:29950")
 -      }
 -      os.Setenv("ARVADOS_EXTERNAL_CLIENT", "")
 -
 -      waitForListener()
 +      kc := runProxy(c, nil, false)
        defer closeListener()
  
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
                c.Check(blocklen, Equals, int64(0))
                log.Print("Finished Get zero block")
        }
 -
 -      log.Print("TestPutAndGet done")
  }
  
  func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
 -      log.Print("TestPutAskGetForbidden start")
 -
 -      kc := runProxy(c, []string{"keepproxy"}, 29951, true)
 -      waitForListener()
 +      kc := runProxy(c, nil, true)
        defer closeListener()
  
        hash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
  
        {
                _, _, err := kc.Ask(hash)
-               c.Check(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 403"), Equals, true)
                log.Print("Ask 1")
        }
  
  
        {
                blocklen, _, err := kc.Ask(hash)
-               c.Assert(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 403"), Equals, true)
                c.Check(blocklen, Equals, int64(0))
                log.Print("Ask 2")
        }
  
        {
                _, blocklen, _, err := kc.Get(hash)
-               c.Assert(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 403"), Equals, true)
                c.Check(blocklen, Equals, int64(0))
                log.Print("Get")
        }
 -
 -      log.Print("TestPutAskGetForbidden done")
  }
  
  func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
 -      log.Print("TestGetDisabled start")
 -
 -      kc := runProxy(c, []string{"keepproxy", "-no-get"}, 29952, false)
 -      waitForListener()
 +      kc := runProxy(c, []string{"-no-get"}, false)
        defer closeListener()
  
        hash := fmt.Sprintf("%x", md5.Sum([]byte("baz")))
  
        {
                _, _, err := kc.Ask(hash)
-               c.Check(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 400"), Equals, true)
                log.Print("Ask 1")
        }
  
  
        {
                blocklen, _, err := kc.Ask(hash)
-               c.Assert(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 400"), Equals, true)
                c.Check(blocklen, Equals, int64(0))
                log.Print("Ask 2")
        }
  
        {
                _, blocklen, _, err := kc.Get(hash)
-               c.Assert(err, Equals, keepclient.BlockNotFound)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound, NotNil)
+               c.Assert(strings.Contains(err.Error(), "HTTP 400"), Equals, true)
                c.Check(blocklen, Equals, int64(0))
                log.Print("Get")
        }
 -
 -      log.Print("TestGetDisabled done")
  }
  
  func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
 -      log.Print("TestPutDisabled start")
 -
 -      kc := runProxy(c, []string{"keepproxy", "-no-put"}, 29953, false)
 -      waitForListener()
 +      kc := runProxy(c, []string{"-no-put"}, false)
        defer closeListener()
  
 -      {
 -              hash2, rep, err := kc.PutB([]byte("quux"))
 -              c.Check(hash2, Equals, "")
 -              c.Check(rep, Equals, 0)
 -              c.Check(err, Equals, keepclient.InsufficientReplicasError)
 -              log.Print("PutB")
 -      }
 -
 -      log.Print("TestPutDisabled done")
 +      hash2, rep, err := kc.PutB([]byte("quux"))
 +      c.Check(hash2, Equals, "")
 +      c.Check(rep, Equals, 0)
 +      c.Check(err, Equals, keepclient.InsufficientReplicasError)
  }
  
  func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 -      runProxy(c, []string{"keepproxy"}, 29954, false)
 -      waitForListener()
 +      runProxy(c, nil, false)
        defer closeListener()
  
        {
                client := http.Client{}
                req, err := http.NewRequest("OPTIONS",
 -                      fmt.Sprintf("http://localhost:29954/%x+3",
 -                              md5.Sum([]byte("foo"))),
 +                      fmt.Sprintf("http://%s/%x+3", listener.Addr().String(), md5.Sum([]byte("foo"))),
                        nil)
                req.Header.Add("Access-Control-Request-Method", "PUT")
                req.Header.Add("Access-Control-Request-Headers", "Authorization, X-Keep-Desired-Replicas")
  
        {
                resp, err := http.Get(
 -                      fmt.Sprintf("http://localhost:29954/%x+3",
 -                              md5.Sum([]byte("foo"))))
 +                      fmt.Sprintf("http://%s/%x+3", listener.Addr().String(), md5.Sum([]byte("foo"))))
                c.Check(err, Equals, nil)
                c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas")
                c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
  }
  
  func (s *ServerRequiredSuite) TestPostWithoutHash(c *C) {
 -      runProxy(c, []string{"keepproxy"}, 29955, false)
 -      waitForListener()
 +      runProxy(c, nil, false)
        defer closeListener()
  
        {
                client := http.Client{}
                req, err := http.NewRequest("POST",
 -                      "http://localhost:29955/",
 +                      "http://"+listener.Addr().String()+"/",
                        strings.NewReader("qux"))
                req.Header.Add("Authorization", "OAuth2 4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
                req.Header.Add("Content-Type", "application/octet-stream")
@@@ -315,7 -444,8 +345,7 @@@ func (s *ServerRequiredSuite) TestStrip
  //   With a valid but non-existing prefix (expect "\n")
  //   With an invalid prefix (expect error)
  func (s *ServerRequiredSuite) TestGetIndex(c *C) {
 -      kc := runProxy(c, []string{"keepproxy"}, 28852, false)
 -      waitForListener()
 +      kc := runProxy(c, nil, false)
        defer closeListener()
  
        // Put "index-data" blocks
                {hash[:3], true, false},  // with matching prefix
                {"abcdef", false, false}, // with no such prefix
        } {
 -              indexReader, err := kc.GetIndex("proxy", spec.prefix)
 +              indexReader, err := kc.GetIndex(TestProxyUUID, spec.prefix)
                c.Assert(err, Equals, nil)
                indexResp, err := ioutil.ReadAll(indexReader)
                c.Assert(err, Equals, nil)
        }
  
        // GetIndex with invalid prefix
 -      _, err = kc.GetIndex("proxy", "xyz")
 +      _, err = kc.GetIndex(TestProxyUUID, "xyz")
        c.Assert((err != nil), Equals, true)
  }
+ func (s *ServerRequiredSuite) TestPutAskGetInvalidToken(c *C) {
+       kc := runProxy(c, []string{"keepproxy"}, 28852, false)
+       waitForListener()
+       defer closeListener()
+       // Put a test block
+       hash, rep, err := kc.PutB([]byte("foo"))
+       c.Check(err, Equals, nil)
+       c.Check(rep, Equals, 2)
+       for _, token := range []string{
+               "nosuchtoken",
+               "2ym314ysp27sk7h943q6vtc378srb06se3pq6ghurylyf3pdmx", // expired
+       } {
+               // Change token to given bad token
+               kc.Arvados.ApiToken = token
+               // Ask should result in error
+               _, _, err = kc.Ask(hash)
+               c.Check(err, NotNil)
+               errNotFound, _ := err.(keepclient.ErrNotFound)
+               c.Check(errNotFound.Temporary(), Equals, false)
+               c.Assert(strings.Contains(err.Error(), "HTTP 403"), Equals, true)
+               // Get should result in error
+               _, _, _, err = kc.Get(hash)
+               c.Check(err, NotNil)
+               errNotFound, _ = err.(keepclient.ErrNotFound)
+               c.Check(errNotFound.Temporary(), Equals, false)
+               c.Assert(strings.Contains(err.Error(), "HTTP 403 \"Missing or invalid Authorization header\""), Equals, true)
+       }
+ }
+ func (s *ServerRequiredSuite) TestAskGetKeepProxyConnectionError(c *C) {
+       arv, err := arvadosclient.MakeArvadosClient()
+       c.Assert(err, Equals, nil)
+       // keepclient with no such keep server
+       kc := keepclient.New(&arv)
+       locals := map[string]string{
+               "proxy": "http://localhost:12345",
+       }
+       kc.SetServiceRoots(locals, nil, nil)
+       // Ask should result in temporary connection refused error
+       hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
+       _, _, err = kc.Ask(hash)
+       c.Check(err, NotNil)
+       errNotFound, _ := err.(*keepclient.ErrNotFound)
+       c.Check(errNotFound.Temporary(), Equals, true)
+       c.Assert(strings.Contains(err.Error(), "connection refused"), Equals, true)
+       // Get should result in temporary connection refused error
+       _, _, _, err = kc.Get(hash)
+       c.Check(err, NotNil)
+       errNotFound, _ = err.(*keepclient.ErrNotFound)
+       c.Check(errNotFound.Temporary(), Equals, true)
+       c.Assert(strings.Contains(err.Error(), "connection refused"), Equals, true)
+ }
+ func (s *NoKeepServerSuite) TestAskGetNoKeepServerError(c *C) {
+       kc := runProxy(c, []string{"keepproxy"}, 29999, false)
+       waitForListener()
+       defer closeListener()
+       // Ask should result in temporary connection refused error
+       hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
+       _, _, err := kc.Ask(hash)
+       c.Check(err, NotNil)
+       errNotFound, _ := err.(*keepclient.ErrNotFound)
+       c.Check(errNotFound.Temporary(), Equals, true)
+       c.Assert(strings.Contains(err.Error(), "HTTP 502"), Equals, true)
+       // Get should result in temporary connection refused error
+       _, _, _, err = kc.Get(hash)
+       c.Check(err, NotNil)
+       errNotFound, _ = err.(*keepclient.ErrNotFound)
+       c.Check(errNotFound.Temporary(), Equals, true)
+       c.Assert(strings.Contains(err.Error(), "HTTP 502"), Equals, true)
+ }