5824: Move HTTP server code to SDK.
[arvados.git] / sdk / go / keepclient / keepclient.go
index 29352b42bba0390acd4ba109e387d3ab28f1f5fe..f82e5c7c594062f23da7ab42db3c4971738d5597 100644 (file)
@@ -24,9 +24,10 @@ const BLOCKSIZE = 64 * 1024 * 1024
 
 var BlockNotFound = errors.New("Block not found")
 var InsufficientReplicasError = errors.New("Could not write sufficient replicas")
-var OversizeBlockError = errors.New("Exceeded maximum block size ("+strconv.Itoa(BLOCKSIZE)+")")
+var OversizeBlockError = errors.New("Exceeded maximum block size (" + strconv.Itoa(BLOCKSIZE) + ")")
 var MissingArvadosApiHost = errors.New("Missing required environment variable ARVADOS_API_HOST")
 var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN")
+var InvalidLocatorError = errors.New("Invalid locator")
 
 const X_Keep_Desired_Replicas = "X-Keep-Desired-Replicas"
 const X_Keep_Replicas_Stored = "X-Keep-Replicas-Stored"
@@ -37,6 +38,7 @@ type KeepClient struct {
        Want_replicas int
        Using_proxy   bool
        localRoots    *map[string]string
+       writableLocalRoots *map[string]string
        gatewayRoots  *map[string]string
        lock          sync.RWMutex
        Client        *http.Client
@@ -125,7 +127,7 @@ func (kc *KeepClient) PutR(r io.Reader) (locator string, replicas int, err error
 func (kc *KeepClient) Get(locator string) (io.ReadCloser, int64, string, error) {
        var errs []string
        for _, host := range kc.getSortedRoots(locator) {
-               url := host+"/"+locator
+               url := host + "/" + locator
                req, err := http.NewRequest("GET", url, nil)
                if err != nil {
                        continue
@@ -147,8 +149,8 @@ func (kc *KeepClient) Get(locator string) (io.ReadCloser, int64, string, error)
                }
                return HashCheckingReader{
                        Reader: resp.Body,
-                       Hash: md5.New(),
-                       Check: locator[0:32],
+                       Hash:   md5.New(),
+                       Check:  locator[0:32],
                }, resp.ContentLength, url, nil
        }
        log.Printf("DEBUG: GET %s failed: %v", locator, errs)
@@ -164,7 +166,7 @@ func (kc *KeepClient) Get(locator string) (io.ReadCloser, int64, string, error)
 // and the URI reporting the data size.
 func (kc *KeepClient) Ask(locator string) (int64, string, error) {
        for _, host := range kc.getSortedRoots(locator) {
-               url := host+"/"+locator
+               url := host + "/" + locator
                req, err := http.NewRequest("HEAD", url, nil)
                if err != nil {
                        continue
@@ -193,6 +195,14 @@ func (kc *KeepClient) GatewayRoots() map[string]string {
        return *kc.gatewayRoots
 }
 
+// WritableLocalRoots() returns the map of writable local Keep services:
+// uuid -> baseURI.
+func (kc *KeepClient) WritableLocalRoots() map[string]string {
+       kc.lock.RLock()
+       defer kc.lock.RUnlock()
+       return *kc.writableLocalRoots
+}
+
 // SetServiceRoots updates the localRoots and gatewayRoots maps,
 // without risk of disrupting operations that are already in progress.
 //
@@ -200,18 +210,26 @@ func (kc *KeepClient) GatewayRoots() map[string]string {
 // caller can reuse/modify them after SetServiceRoots returns, but
 // they should not be modified by any other goroutine while
 // SetServiceRoots is running.
-func (kc *KeepClient) SetServiceRoots(newLocals, newGateways map[string]string) {
+func (kc *KeepClient) SetServiceRoots(newLocals, newWritableLocals map[string]string, newGateways map[string]string) {
        locals := make(map[string]string)
        for uuid, root := range newLocals {
                locals[uuid] = root
        }
+
+       writables := make(map[string]string)
+       for uuid, root := range newWritableLocals {
+               writables[uuid] = root
+       }
+
        gateways := make(map[string]string)
        for uuid, root := range newGateways {
                gateways[uuid] = root
        }
+
        kc.lock.Lock()
        defer kc.lock.Unlock()
        kc.localRoots = &locals
+       kc.writableLocalRoots = &writables
        kc.gatewayRoots = &gateways
 }
 
@@ -244,44 +262,36 @@ func (kc *KeepClient) getSortedRoots(locator string) []string {
 }
 
 type Locator struct {
-       Hash      string
-       Size      int
-       Signature string
-       Timestamp string
+       Hash  string
+       Size  int      // -1 if data size is not known
+       Hints []string // Including the size hint, if any
 }
 
-func MakeLocator2(hash string, hints string) (locator Locator) {
-       locator.Hash = hash
-       if hints != "" {
-               signature_pat, _ := regexp.Compile("^A([[:xdigit:]]+)@([[:xdigit:]]{8})$")
-               for _, hint := range strings.Split(hints, "+") {
-                       if hint != "" {
-                               if match, _ := regexp.MatchString("^[[:digit:]]+$", hint); match {
-                                       fmt.Sscanf(hint, "%d", &locator.Size)
-                               } else if m := signature_pat.FindStringSubmatch(hint); m != nil {
-                                       locator.Signature = m[1]
-                                       locator.Timestamp = m[2]
-                               } else if match, _ := regexp.MatchString("^[:upper:]", hint); match {
-                                       // Any unknown hint that starts with an uppercase letter is
-                                       // presumed to be valid and ignored, to permit forward compatibility.
-                               } else {
-                                       // Unknown format; not a valid locator.
-                                       return Locator{"", 0, "", ""}
-                               }
-                       }
-               }
+func (loc *Locator) String() string {
+       s := loc.Hash
+       if len(loc.Hints) > 0 {
+               s = s + "+" + strings.Join(loc.Hints, "+")
        }
-       return locator
+       return s
 }
 
-var pathpattern = regexp.MustCompile("^([0-9a-f]{32})([+].*)?$")
+var locatorMatcher = regexp.MustCompile("^([0-9a-f]{32})([+](.*))?$")
 
-func MakeLocator(path string) Locator {
-       sm := pathpattern.FindStringSubmatch(path)
+func MakeLocator(path string) (*Locator, error) {
+       sm := locatorMatcher.FindStringSubmatch(path)
        if sm == nil {
-               log.Print("Failed match ", path)
-               return Locator{"", 0, "", ""}
+               return nil, InvalidLocatorError
        }
-
-       return MakeLocator2(sm[1], sm[2])
+       loc := Locator{Hash: sm[1], Size: -1}
+       if sm[2] != "" {
+               loc.Hints = strings.Split(sm[3], "+")
+       } else {
+               loc.Hints = []string{}
+       }
+       if len(loc.Hints) > 0 {
+               if size, err := strconv.Atoi(loc.Hints[0]); err == nil {
+                       loc.Size = size
+               }
+       }
+       return &loc, nil
 }