20318: Fix DiskCacheSize not propagated by (*KeepClient)Clone().
[arvados.git] / sdk / go / keepclient / keepclient.go
index 68ac886ddd8ede2809a56a577baedfd67604e7d1..3dc0aa0389158268af38861ec202574d8f20211c 100644 (file)
@@ -16,6 +16,8 @@ import (
        "io/ioutil"
        "net"
        "net/http"
+       "os"
+       "path/filepath"
        "regexp"
        "strconv"
        "strings"
@@ -40,6 +42,9 @@ var (
        DefaultProxyConnectTimeout      = 30 * time.Second
        DefaultProxyTLSHandshakeTimeout = 10 * time.Second
        DefaultProxyKeepAlive           = 120 * time.Second
+
+       rootCacheDir = "/var/cache/arvados/keep"
+       userCacheDir = ".cache/arvados/keep" // relative to HOME
 )
 
 // Error interface with an error and boolean indicating whether the error is temporary
@@ -95,6 +100,8 @@ type HTTPClient interface {
        Do(*http.Request) (*http.Response, error)
 }
 
+const DiskCacheDisabled = arvados.ByteSizeOrPercent(1)
+
 // KeepClient holds information about Arvados and Keep servers.
 type KeepClient struct {
        Arvados               *arvadosclient.ArvadosClient
@@ -105,10 +112,10 @@ type KeepClient struct {
        lock                  sync.RWMutex
        HTTPClient            HTTPClient
        Retries               int
-       BlockCache            *BlockCache
        RequestID             string
        StorageClasses        []string
-       DefaultStorageClasses []string // Set by cluster's exported config
+       DefaultStorageClasses []string                  // Set by cluster's exported config
+       DiskCacheSize         arvados.ByteSizeOrPercent // See also DiskCacheDisabled
 
        // set to 1 if all writable services are of disk type, otherwise 0
        replicasPerService int
@@ -118,6 +125,29 @@ type KeepClient struct {
 
        // Disable automatic discovery of keep services
        disableDiscovery bool
+
+       gatewayStack arvados.KeepGateway
+}
+
+func (kc *KeepClient) Clone() *KeepClient {
+       kc.lock.Lock()
+       defer kc.lock.Unlock()
+       return &KeepClient{
+               Arvados:               kc.Arvados,
+               Want_replicas:         kc.Want_replicas,
+               localRoots:            kc.localRoots,
+               writableLocalRoots:    kc.writableLocalRoots,
+               gatewayRoots:          kc.gatewayRoots,
+               HTTPClient:            kc.HTTPClient,
+               Retries:               kc.Retries,
+               RequestID:             kc.RequestID,
+               StorageClasses:        kc.StorageClasses,
+               DefaultStorageClasses: kc.DefaultStorageClasses,
+               DiskCacheSize:         kc.DiskCacheSize,
+               replicasPerService:    kc.replicasPerService,
+               foundNonDiskSvc:       kc.foundNonDiskSvc,
+               disableDiscovery:      kc.disableDiscovery,
+       }
 }
 
 func (kc *KeepClient) loadDefaultClasses() error {
@@ -332,26 +362,50 @@ func (kc *KeepClient) getOrHead(method string, locator string, header http.Heade
        return nil, 0, "", nil, err
 }
 
+// attempt to create dir/subdir/ and its parents, up to but not
+// including dir itself, using mode 0700.
+func makedirs(dir, subdir string) {
+       for _, part := range strings.Split(subdir, string(os.PathSeparator)) {
+               dir = filepath.Join(dir, part)
+               os.Mkdir(dir, 0700)
+       }
+}
+
+// upstreamGateway creates/returns the KeepGateway stack used to read
+// and write data: a disk-backed cache on top of an http backend.
+func (kc *KeepClient) upstreamGateway() arvados.KeepGateway {
+       kc.lock.Lock()
+       defer kc.lock.Unlock()
+       if kc.gatewayStack != nil {
+               return kc.gatewayStack
+       }
+       var cachedir string
+       if os.Geteuid() == 0 {
+               cachedir = rootCacheDir
+               makedirs("/", cachedir)
+       } else {
+               home := "/" + os.Getenv("HOME")
+               makedirs(home, userCacheDir)
+               cachedir = filepath.Join(home, userCacheDir)
+       }
+       backend := &keepViaHTTP{kc}
+       if kc.DiskCacheSize == DiskCacheDisabled {
+               kc.gatewayStack = backend
+       } else {
+               kc.gatewayStack = &arvados.DiskCache{
+                       Dir:         cachedir,
+                       MaxSize:     kc.DiskCacheSize,
+                       KeepGateway: backend,
+               }
+       }
+       return kc.gatewayStack
+}
+
 // LocalLocator returns a locator equivalent to the one supplied, but
 // with a valid signature from the local cluster. If the given locator
 // already has a local signature, it is returned unchanged.
 func (kc *KeepClient) LocalLocator(locator string) (string, error) {
-       if !strings.Contains(locator, "+R") {
-               // Either it has +A, or it's unsigned and we assume
-               // it's a local locator on a site with signatures
-               // disabled.
-               return locator, nil
-       }
-       sighdr := fmt.Sprintf("local, time=%s", time.Now().UTC().Format(time.RFC3339))
-       _, _, url, hdr, err := kc.getOrHead("HEAD", locator, http.Header{"X-Keep-Signature": []string{sighdr}})
-       if err != nil {
-               return "", err
-       }
-       loc := hdr.Get("X-Keep-Locator")
-       if loc == "" {
-               return "", fmt.Errorf("missing X-Keep-Locator header in HEAD response from %s", url)
-       }
-       return loc, nil
+       return kc.upstreamGateway().LocalLocator(locator)
 }
 
 // Get retrieves a block, given a locator. Returns a reader, the
@@ -369,7 +423,13 @@ func (kc *KeepClient) Get(locator string) (io.ReadCloser, int64, string, error)
 // ReadAt retrieves a portion of block from the cache if it's
 // present, otherwise from the network.
 func (kc *KeepClient) ReadAt(locator string, p []byte, off int) (int, error) {
-       return kc.cache().ReadAt(kc, locator, p, off)
+       return kc.upstreamGateway().ReadAt(locator, p, off)
+}
+
+// BlockWrite writes a full block to upstream servers and saves a copy
+// in the local cache.
+func (kc *KeepClient) BlockWrite(ctx context.Context, req arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
+       return kc.upstreamGateway().BlockWrite(ctx, req)
 }
 
 // Ask verifies that a block with the given hash is available and
@@ -511,17 +571,6 @@ func (kc *KeepClient) getSortedRoots(locator string) []string {
        return found
 }
 
-func (kc *KeepClient) cache() *BlockCache {
-       if kc.BlockCache != nil {
-               return kc.BlockCache
-       }
-       return DefaultBlockCache
-}
-
-func (kc *KeepClient) ClearBlockCache() {
-       kc.cache().Clear()
-}
-
 func (kc *KeepClient) SetStorageClasses(sc []string) {
        // make a copy so the caller can't mess with it.
        kc.StorageClasses = append([]string{}, sc...)