Merge branch '9005-share-discovery'
authorTom Clegg <tom@curoverse.com>
Mon, 5 Jun 2017 14:06:26 +0000 (10:06 -0400)
committerTom Clegg <tom@curoverse.com>
Mon, 5 Jun 2017 14:06:26 +0000 (10:06 -0400)
closes #9005

17 files changed:
build/run-tests.sh
sdk/go/arvados/client.go
sdk/go/arvadosclient/arvadosclient.go
sdk/go/keepclient/discover.go
sdk/go/keepclient/discover_test.go
sdk/go/keepclient/keepclient.go
sdk/go/keepclient/keepclient_test.go
sdk/go/keepclient/support.go
services/api/app/controllers/arvados/v1/keep_services_controller.rb
services/api/test/functional/arvados/v1/keep_services_controller_test.rb
services/keep-web/handler.go
services/keepproxy/keepproxy.go
services/keepstore/pull_worker_integration_test.go
tools/keep-block-check/keep-block-check.go
tools/keep-block-check/keep-block-check_test.go
tools/keep-rsync/keep-rsync.go
tools/keep-rsync/keep-rsync_test.go

index b6a93d471211a41a3d97c6aa8bbe11e3b033e130..dffbe32e8ab9c89d6cecf02dd445c8314d2560ad 100755 (executable)
@@ -567,7 +567,7 @@ do_test_once() {
         # mode makes Go show the wrong line numbers when reporting
         # compilation errors.
         go get -t "git.curoverse.com/arvados.git/$1" && \
-            cd "$WORKSPACE/$1" && \
+            cd "$GOPATH/src/git.curoverse.com/arvados.git/$1" && \
             [[ -z "$(gofmt -e -d . | tee /dev/stderr)" ]] && \
             if [[ -n "${testargs[$1]}" ]]
         then
index 9691e7a07e475668cc73a80dffba466addb3b4ef..d7eb811b8a7a26a08931b07b9c66fcfec83d78a3 100644 (file)
@@ -6,6 +6,7 @@ import (
        "fmt"
        "io"
        "io/ioutil"
+       "log"
        "math"
        "net/http"
        "net/url"
@@ -63,13 +64,25 @@ var DefaultSecureClient = &http.Client{
 // ARVADOS_API_* environment variables.
 func NewClientFromEnv() *Client {
        var svcs []string
-       if s := os.Getenv("ARVADOS_KEEP_SERVICES"); s != "" {
-               svcs = strings.Split(s, " ")
+       for _, s := range strings.Split(os.Getenv("ARVADOS_KEEP_SERVICES"), " ") {
+               if s == "" {
+                       continue
+               } else if u, err := url.Parse(s); err != nil {
+                       log.Printf("ARVADOS_KEEP_SERVICES: %q: %s", s, err)
+               } else if !u.IsAbs() {
+                       log.Printf("ARVADOS_KEEP_SERVICES: %q: not an absolute URI", s)
+               } else {
+                       svcs = append(svcs, s)
+               }
+       }
+       var insecure bool
+       if s := strings.ToLower(os.Getenv("ARVADOS_API_HOST_INSECURE")); s == "1" || s == "yes" || s == "true" {
+               insecure = true
        }
        return &Client{
                APIHost:         os.Getenv("ARVADOS_API_HOST"),
                AuthToken:       os.Getenv("ARVADOS_API_TOKEN"),
-               Insecure:        os.Getenv("ARVADOS_API_HOST_INSECURE") != "",
+               Insecure:        insecure,
                KeepServiceURIs: svcs,
        }
 }
index af7f028e0725635fefbae6b880d1506e788b2dd1..4cfda94581518fd9360ebc4f3268e231893797c7 100644 (file)
@@ -74,6 +74,13 @@ func (e APIServerError) Error() string {
        }
 }
 
+// StringBool tests whether s is suggestive of true. It returns true
+// if s is a mixed/uppoer/lower-case variant of "1", "yes", or "true".
+func StringBool(s string) bool {
+       s = strings.ToLower(s)
+       return s == "1" || s == "yes" || s == "true"
+}
+
 // Helper type so we don't have to write out 'map[string]interface{}' every time.
 type Dict map[string]interface{}
 
@@ -163,6 +170,7 @@ func New(c *arvados.Client) (*ArvadosClient, error) {
                        TLSClientConfig: MakeTLSConfig(c.Insecure)}},
                External:          false,
                Retries:           2,
+               KeepServiceURIs:   c.KeepServiceURIs,
                lastClosedIdlesAt: time.Now(),
        }
 
@@ -174,42 +182,12 @@ func New(c *arvados.Client) (*ArvadosClient, error) {
 // ARVADOS_API_HOST_INSECURE, ARVADOS_EXTERNAL_CLIENT, and
 // ARVADOS_KEEP_SERVICES.
 func MakeArvadosClient() (ac *ArvadosClient, err error) {
-       var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
-       insecure := matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
-       external := matchTrue.MatchString(os.Getenv("ARVADOS_EXTERNAL_CLIENT"))
-
-       ac = &ArvadosClient{
-               Scheme:      "https",
-               ApiServer:   os.Getenv("ARVADOS_API_HOST"),
-               ApiToken:    os.Getenv("ARVADOS_API_TOKEN"),
-               ApiInsecure: insecure,
-               Client: &http.Client{Transport: &http.Transport{
-                       TLSClientConfig: MakeTLSConfig(insecure)}},
-               External: external,
-               Retries:  2}
-
-       for _, s := range strings.Split(os.Getenv("ARVADOS_KEEP_SERVICES"), " ") {
-               if s == "" {
-                       continue
-               }
-               if u, err := url.Parse(s); err != nil {
-                       return ac, fmt.Errorf("ARVADOS_KEEP_SERVICES: %q: %s", s, err)
-               } else if !u.IsAbs() {
-                       return ac, fmt.Errorf("ARVADOS_KEEP_SERVICES: %q: not an absolute URI", s)
-               }
-               ac.KeepServiceURIs = append(ac.KeepServiceURIs, s)
-       }
-
-       if ac.ApiServer == "" {
-               return ac, MissingArvadosApiHost
-       }
-       if ac.ApiToken == "" {
-               return ac, MissingArvadosApiToken
+       ac, err = New(arvados.NewClientFromEnv())
+       if err != nil {
+               return
        }
-
-       ac.lastClosedIdlesAt = time.Now()
-
-       return ac, err
+       ac.External = StringBool(os.Getenv("ARVADOS_EXTERNAL_CLIENT"))
+       return
 }
 
 // CallRaw is the same as Call() but returns a Reader that reads the
index 8889c4bedae23f52d62667ba5b8747ed11c3f67b..e2cd329fc4c22ccfdb73ff6099ce8c95f6f28d16 100644 (file)
@@ -6,99 +6,165 @@ import (
        "log"
        "os"
        "os/signal"
-       "reflect"
        "strings"
+       "sync"
        "syscall"
        "time"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
 )
 
-// DiscoverKeepServers gets list of available keep services from the
-// API server.
-//
-// If a list of services is provided in the arvadosclient (e.g., from
-// an environment variable or local config), that list is used
-// instead.
-func (this *KeepClient) DiscoverKeepServers() error {
-       if this.Arvados.KeepServiceURIs != nil {
-               this.foundNonDiskSvc = true
-               this.replicasPerService = 0
-               roots := make(map[string]string)
-               for i, uri := range this.Arvados.KeepServiceURIs {
-                       roots[fmt.Sprintf("00000-bi6l4-%015d", i)] = uri
-               }
-               this.SetServiceRoots(roots, roots, roots)
-               return nil
+// ClearCache clears the Keep service discovery cache.
+func RefreshServiceDiscovery() {
+       svcListCacheMtx.Lock()
+       defer svcListCacheMtx.Unlock()
+       for _, ent := range svcListCache {
+               ent.clear <- struct{}{}
        }
+}
 
-       // ArvadosClient did not provide a services list. Ask API
-       // server for a list of accessible services.
-       var list svcList
-       err := this.Arvados.Call("GET", "keep_services", "", "accessible", nil, &list)
-       if err != nil {
-               return err
+// ClearCacheOnSIGHUP installs a signal handler that calls
+// ClearCache when SIGHUP is received.
+func RefreshServiceDiscoveryOnSIGHUP() {
+       svcListCacheMtx.Lock()
+       defer svcListCacheMtx.Unlock()
+       if svcListCacheSignal != nil {
+               return
        }
-       return this.loadKeepServers(list)
+       svcListCacheSignal = make(chan os.Signal, 1)
+       signal.Notify(svcListCacheSignal, syscall.SIGHUP)
+       go func() {
+               for range svcListCacheSignal {
+                       RefreshServiceDiscovery()
+               }
+       }()
 }
 
-// LoadKeepServicesFromJSON gets list of available keep services from given JSON
-func (this *KeepClient) LoadKeepServicesFromJSON(services string) error {
-       var list svcList
-
-       // Load keep services from given json
-       dec := json.NewDecoder(strings.NewReader(services))
-       if err := dec.Decode(&list); err != nil {
-               return err
-       }
+var (
+       svcListCache       = map[string]cachedSvcList{}
+       svcListCacheSignal chan os.Signal
+       svcListCacheMtx    sync.Mutex
+)
 
-       return this.loadKeepServers(list)
+type cachedSvcList struct {
+       arv    *arvadosclient.ArvadosClient
+       latest chan svcList
+       clear  chan struct{}
 }
 
-// RefreshServices calls DiscoverKeepServers to 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 (kc *KeepClient) RefreshServices(interval, errInterval time.Duration) {
-       var previousRoots = []map[string]string{}
-
-       timer := time.NewTimer(interval)
-       gotHUP := make(chan os.Signal, 1)
-       signal.Notify(gotHUP, syscall.SIGHUP)
+// Check for new services list every few minutes. Send the latest list
+// to the "latest" channel as needed.
+func (ent *cachedSvcList) poll() {
+       wakeup := make(chan struct{})
+
+       replace := make(chan svcList)
+       go func() {
+               wakeup <- struct{}{}
+               current := <-replace
+               for {
+                       select {
+                       case <-ent.clear:
+                               wakeup <- struct{}{}
+                               // Wait here for the next success, in
+                               // order to avoid returning stale
+                               // results on the "latest" channel.
+                               current = <-replace
+                       case current = <-replace:
+                       case ent.latest <- current:
+                       }
+               }
+       }()
 
+       okDelay := 5 * time.Minute
+       errDelay := 3 * time.Second
+       timer := time.NewTimer(okDelay)
        for {
                select {
-               case <-gotHUP:
                case <-timer.C:
+               case <-wakeup:
+                       if !timer.Stop() {
+                               // Lost race stopping timer; skip extra firing
+                               <-timer.C
+                       }
                }
-               timer.Reset(interval)
-
-               if err := kc.DiscoverKeepServers(); err != nil {
-                       log.Printf("WARNING: Error retrieving services list: %v (retrying in %v)", err, errInterval)
-                       timer.Reset(errInterval)
+               var next svcList
+               err := ent.arv.Call("GET", "keep_services", "", "accessible", nil, &next)
+               if err != nil {
+                       log.Printf("WARNING: Error retrieving services list: %v (retrying in %v)", err, errDelay)
+                       timer.Reset(errDelay)
                        continue
                }
-               newRoots := []map[string]string{kc.LocalRoots(), kc.GatewayRoots()}
+               replace <- next
+               timer.Reset(okDelay)
+       }
+}
+
+// discoverServices gets the list of available keep services from
+// the API server.
+//
+// If a list of services is provided in the arvadosclient (e.g., from
+// an environment variable or local config), that list is used
+// instead.
+//
+// If an API call is made, the result is cached for 5 minutes or until
+// ClearCache() is called, and during this interval it is reused by
+// other KeepClients that use the same API server host.
+func (kc *KeepClient) discoverServices() error {
+       if kc.disableDiscovery {
+               return nil
+       }
 
-               if !reflect.DeepEqual(previousRoots, newRoots) {
-                       DebugPrintf("DEBUG: Updated services list: locals %v gateways %v", newRoots[0], newRoots[1])
-                       previousRoots = newRoots
+       if kc.Arvados.KeepServiceURIs != nil {
+               kc.disableDiscovery = true
+               kc.foundNonDiskSvc = true
+               kc.replicasPerService = 0
+               roots := make(map[string]string)
+               for i, uri := range kc.Arvados.KeepServiceURIs {
+                       roots[fmt.Sprintf("00000-bi6l4-%015d", i)] = uri
                }
+               kc.setServiceRoots(roots, roots, roots)
+               return nil
+       }
 
-               if len(newRoots[0]) == 0 {
-                       log.Printf("WARNING: No local services (retrying in %v)", errInterval)
-                       timer.Reset(errInterval)
+       svcListCacheMtx.Lock()
+       cacheEnt, ok := svcListCache[kc.Arvados.ApiServer]
+       if !ok {
+               arv := *kc.Arvados
+               cacheEnt = cachedSvcList{
+                       latest: make(chan svcList),
+                       clear:  make(chan struct{}),
+                       arv:    &arv,
                }
+               go cacheEnt.poll()
+               svcListCache[kc.Arvados.ApiServer] = cacheEnt
+       }
+       svcListCacheMtx.Unlock()
+
+       return kc.loadKeepServers(<-cacheEnt.latest)
+}
+
+// LoadKeepServicesFromJSON gets list of available keep services from
+// given JSON and disables automatic service discovery.
+func (kc *KeepClient) LoadKeepServicesFromJSON(services string) error {
+       kc.disableDiscovery = true
+
+       var list svcList
+       dec := json.NewDecoder(strings.NewReader(services))
+       if err := dec.Decode(&list); err != nil {
+               return err
        }
+
+       return kc.loadKeepServers(list)
 }
 
-// loadKeepServers
-func (this *KeepClient) loadKeepServers(list svcList) error {
+func (kc *KeepClient) loadKeepServers(list svcList) error {
        listed := make(map[string]bool)
        localRoots := make(map[string]string)
        gatewayRoots := make(map[string]string)
        writableLocalRoots := make(map[string]string)
 
        // replicasPerService is 1 for disks; unknown or unlimited otherwise
-       this.replicasPerService = 1
+       kc.replicasPerService = 1
 
        for _, service := range list.Items {
                scheme := "http"
@@ -117,12 +183,12 @@ func (this *KeepClient) loadKeepServers(list svcList) error {
                if service.ReadOnly == false {
                        writableLocalRoots[service.Uuid] = url
                        if service.SvcType != "disk" {
-                               this.replicasPerService = 0
+                               kc.replicasPerService = 0
                        }
                }
 
                if service.SvcType != "disk" {
-                       this.foundNonDiskSvc = true
+                       kc.foundNonDiskSvc = true
                }
 
                // Gateway services are only used when specified by
@@ -133,6 +199,6 @@ func (this *KeepClient) loadKeepServers(list svcList) error {
                gatewayRoots[service.Uuid] = url
        }
 
-       this.SetServiceRoots(localRoots, writableLocalRoots, gatewayRoots)
+       kc.setServiceRoots(localRoots, writableLocalRoots, gatewayRoots)
        return nil
 }
index 379d44c820aec0e0b88d84b3c52e5fd316480844..4065ce342e43dfaa5172ca48f4a3ec3282045296 100644 (file)
@@ -3,28 +3,15 @@ package keepclient
 import (
        "crypto/md5"
        "fmt"
-       "gopkg.in/check.v1"
        "net/http"
        "os"
-       "time"
+
+       "gopkg.in/check.v1"
 
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
 )
 
-func ExampleKeepClient_RefreshServices() {
-       arv, err := arvadosclient.MakeArvadosClient()
-       if err != nil {
-               panic(err)
-       }
-       kc, err := MakeKeepClient(arv)
-       if err != nil {
-               panic(err)
-       }
-       go kc.RefreshServices(5*time.Minute, 3*time.Second)
-       fmt.Printf("LocalRoots: %#v\n", kc.LocalRoots())
-}
-
 func (s *ServerRequiredSuite) TestOverrideDiscovery(c *check.C) {
        defer os.Setenv("ARVADOS_KEEP_SERVICES", "")
 
index 76ea17517fc6d2b59e0c96e2a33fb3561fcd38df..029c6ee7f3a5834a8af149b64917aff2d5dc4bcb 100644 (file)
@@ -88,9 +88,9 @@ type HTTPClient interface {
 type KeepClient struct {
        Arvados            *arvadosclient.ArvadosClient
        Want_replicas      int
-       localRoots         *map[string]string
-       writableLocalRoots *map[string]string
-       gatewayRoots       *map[string]string
+       localRoots         map[string]string
+       writableLocalRoots map[string]string
+       gatewayRoots       map[string]string
        lock               sync.RWMutex
        HTTPClient         HTTPClient
        Retries            int
@@ -101,6 +101,9 @@ type KeepClient struct {
 
        // Any non-disk typed services found in the list of keepservers?
        foundNonDiskSvc bool
+
+       // Disable automatic discovery of keep services
+       disableDiscovery bool
 }
 
 // MakeKeepClient creates a new KeepClient, calls
@@ -108,12 +111,11 @@ type KeepClient struct {
 // use.
 func MakeKeepClient(arv *arvadosclient.ArvadosClient) (*KeepClient, error) {
        kc := New(arv)
-       return kc, kc.DiscoverKeepServers()
+       return kc, kc.discoverServices()
 }
 
-// New creates a new KeepClient. The caller must call
-// DiscoverKeepServers() before using the returned client to read or
-// write data.
+// New creates a new KeepClient. Service discovery will occur on the
+// next read/write operation.
 func New(arv *arvadosclient.ArvadosClient) *KeepClient {
        defaultReplicationLevel := 2
        value, err := arv.Discovery("defaultCollectionReplication")
@@ -349,55 +351,47 @@ func (kc *KeepClient) GetIndex(keepServiceUUID, prefix string) (io.Reader, error
 // LocalRoots() returns the map of local (i.e., disk and proxy) Keep
 // services: uuid -> baseURI.
 func (kc *KeepClient) LocalRoots() map[string]string {
+       kc.discoverServices()
        kc.lock.RLock()
        defer kc.lock.RUnlock()
-       return *kc.localRoots
+       return kc.localRoots
 }
 
 // GatewayRoots() returns the map of Keep remote gateway services:
 // uuid -> baseURI.
 func (kc *KeepClient) GatewayRoots() map[string]string {
+       kc.discoverServices()
        kc.lock.RLock()
        defer kc.lock.RUnlock()
-       return *kc.gatewayRoots
+       return kc.gatewayRoots
 }
 
 // WritableLocalRoots() returns the map of writable local Keep services:
 // uuid -> baseURI.
 func (kc *KeepClient) WritableLocalRoots() map[string]string {
+       kc.discoverServices()
        kc.lock.RLock()
        defer kc.lock.RUnlock()
-       return *kc.writableLocalRoots
+       return kc.writableLocalRoots
 }
 
-// SetServiceRoots updates the localRoots and gatewayRoots maps,
-// without risk of disrupting operations that are already in progress.
+// SetServiceRoots disables service discovery and updates the
+// localRoots and gatewayRoots maps, without disrupting operations
+// that are already in progress.
 //
-// The KeepClient makes its own copy of the supplied maps, so the
-// 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, newWritableLocals, 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
-       }
+// The supplied maps must not be modified after calling
+// SetServiceRoots.
+func (kc *KeepClient) SetServiceRoots(locals, writables, gateways map[string]string) {
+       kc.disableDiscovery = true
+       kc.setServiceRoots(locals, writables, gateways)
+}
 
+func (kc *KeepClient) setServiceRoots(locals, writables, gateways map[string]string) {
        kc.lock.Lock()
        defer kc.lock.Unlock()
-       kc.localRoots = &locals
-       kc.writableLocalRoots = &writables
-       kc.gatewayRoots = &gateways
+       kc.localRoots = locals
+       kc.writableLocalRoots = writables
+       kc.gatewayRoots = gateways
 }
 
 // getSortedRoots returns a list of base URIs of Keep services, in the
index d2b84e94259892bcba2efe07e1952126530c8f7c..724d7ff3214db2315053d5e94f09e5e91adb070e 100644 (file)
@@ -35,6 +35,10 @@ type ServerRequiredSuite struct{}
 // Standalone tests
 type StandaloneSuite struct{}
 
+func (s *StandaloneSuite) SetUpTest(c *C) {
+       RefreshServiceDiscovery()
+}
+
 func pythonDir() string {
        cwd, _ := os.Getwd()
        return fmt.Sprintf("%s/../../python/tests", cwd)
@@ -50,6 +54,10 @@ func (s *ServerRequiredSuite) TearDownSuite(c *C) {
        arvadostest.StopAPI()
 }
 
+func (s *ServerRequiredSuite) SetUpTest(c *C) {
+       RefreshServiceDiscovery()
+}
+
 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, Equals, nil)
@@ -1067,12 +1075,14 @@ func (s *StandaloneSuite) TestGetIndexWithNoPrefix(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
-       kc, _ := MakeKeepClient(arv)
+       c.Assert(err, IsNil)
+       kc, err := MakeKeepClient(arv)
+       c.Assert(err, IsNil)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
 
        r, err := kc.GetIndex("x", "")
-       c.Check(err, Equals, nil)
+       c.Check(err, IsNil)
 
        content, err2 := ioutil.ReadAll(r)
        c.Check(err2, Equals, nil)
@@ -1098,7 +1108,7 @@ func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
 
        r, err := kc.GetIndex("x", hash[0:3])
-       c.Check(err, Equals, nil)
+       c.Assert(err, Equals, nil)
 
        content, err2 := ioutil.ReadAll(r)
        c.Check(err2, Equals, nil)
@@ -1237,6 +1247,7 @@ func (s *ServerRequiredSuite) TestMakeKeepClientWithNonDiskTypeService(c *C) {
                &blobKeepService)
        c.Assert(err, Equals, nil)
        defer func() { arv.Delete("keep_services", blobKeepService["uuid"].(string), nil, nil) }()
+       RefreshServiceDiscovery()
 
        // Make a keepclient and ensure that the testblobstore is included
        kc, err := MakeKeepClient(arv)
index 0e74892a40951af958ba19137930d4ca63121b0a..8545cb80b855d9e53606042bfc940df7388e51eb 100644 (file)
@@ -10,9 +10,9 @@ import (
        "math/rand"
        "net/http"
        "os"
-       "regexp"
        "strings"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/streamer"
 )
 
@@ -22,8 +22,7 @@ import (
 var DebugPrintf = func(string, ...interface{}) {}
 
 func init() {
-       var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
-       if matchTrue.MatchString(os.Getenv("ARVADOS_DEBUG")) {
+       if arvadosclient.StringBool(os.Getenv("ARVADOS_DEBUG")) {
                DebugPrintf = log.Printf
        }
 }
index d2a512bde75f68c790360813b47872b748481be1..e59c5f25789e4e83a9be0a215a784df6742d8eb2 100644 (file)
@@ -2,6 +2,7 @@ class Arvados::V1::KeepServicesController < ApplicationController
 
   skip_before_filter :find_object_by_uuid, only: :accessible
   skip_before_filter :render_404_if_no_object, only: :accessible
+  skip_before_filter :require_auth_scope, only: :accessible
 
   def find_objects_for_index
     # all users can list all keep services
index 1375d4c9ce71549bdb43988cf4808eb70a908bf2..706f73ffda6157812e9cb687c9340f344464c663 100644 (file)
@@ -20,9 +20,9 @@ class Arvados::V1::KeepServicesControllerTest < ActionController::TestCase
     assert_equal true, assigns(:objects).any?
   end
 
-  [:admin, :active, :inactive, :anonymous].each do |u|
-    test "accessible to #{u} user" do
-      authorize_with u
+  [:admin, :active, :inactive, :anonymous, nil].each do |u|
+    test "accessible to #{u.inspect} user" do
+      authorize_with(u) if u
       get :accessible
       assert_response :success
       assert_not_empty json_response['items']
index 85ec93b8d9b325f00029613902d3dbda9a125adf..b7e39c6041d256188350d464da86ee39fb29bdeb 100644 (file)
@@ -64,6 +64,7 @@ func parseCollectionIDFromURL(s string) string {
 
 func (h *handler) setup() {
        h.clientPool = arvadosclient.MakeClientPool()
+       keepclient.RefreshServiceDiscoveryOnSIGHUP()
 }
 
 // ServeHTTP implements http.Handler.
index f9239a0f15648b0078a8ea8a00fce3dfc27d9366..7dfd01ad41e7fd96f74b763d6129cc1792448538 100644 (file)
@@ -104,6 +104,7 @@ func main() {
        if err != nil {
                log.Fatalf("Error setting up keep client %s", err.Error())
        }
+       keepclient.RefreshServiceDiscoveryOnSIGHUP()
 
        if cfg.PIDFile != "" {
                f, err := os.Create(cfg.PIDFile)
@@ -133,7 +134,6 @@ func main() {
        if cfg.DefaultReplicas > 0 {
                kc.Want_replicas = cfg.DefaultReplicas
        }
-       go kc.RefreshServices(5*time.Minute, 3*time.Second)
 
        listener, err = net.Listen("tcp", cfg.Listen)
        if err != nil {
index c0a7c6f6a523725098596e5fce89fcbffa7fdc94..34c2f61a37a01097ec1ce257f49f5ccb9c33e246 100644 (file)
@@ -29,24 +29,23 @@ func SetupPullWorkerIntegrationTest(t *testing.T, testData PullWorkIntegrationTe
        // start api and keep servers
        arvadostest.StartAPI()
        arvadostest.StartKeep(2, false)
+       keepclient.RefreshServiceDiscovery()
 
        // make arvadosclient
        arv, err := arvadosclient.MakeArvadosClient()
        if err != nil {
-               t.Error("Error creating arv")
+               t.Fatalf("Error creating arv: %s", err)
        }
 
        // keep client
-       keepClient = &keepclient.KeepClient{
-               Arvados:       arv,
-               Want_replicas: 1,
+       keepClient, err = keepclient.MakeKeepClient(arv)
+       if err != nil {
+               t.Fatalf("error creating KeepClient: %s", err)
        }
+       keepClient.Want_replicas = 1
 
        // discover keep services
        var servers []string
-       if err := keepClient.DiscoverKeepServers(); err != nil {
-               t.Error("Error discovering keep services")
-       }
        for _, host := range keepClient.LocalRoots() {
                servers = append(servers, host)
        }
index 6cf11a728075c60990c1b049c0fb916cea5cd5d5..e22e4b5cfe56e2d127d334ef593d6b5067cc3978 100644 (file)
@@ -5,15 +5,15 @@ import (
        "errors"
        "flag"
        "fmt"
-       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
-       "git.curoverse.com/arvados.git/sdk/go/keepclient"
        "io/ioutil"
        "log"
        "net/http"
        "os"
-       "regexp"
        "strings"
        "time"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
 )
 
 func main() {
@@ -99,8 +99,6 @@ func loadConfig(configFile string) (config apiConfig, blobSigningKey string, err
        return
 }
 
-var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
-
 // Read config from file
 func readConfigFromFile(filename string) (config apiConfig, blobSigningKey string, err error) {
        if !strings.Contains(filename, "/") {
@@ -130,9 +128,9 @@ func readConfigFromFile(filename string) (config apiConfig, blobSigningKey strin
                        case "ARVADOS_API_HOST":
                                config.APIHost = value
                        case "ARVADOS_API_HOST_INSECURE":
-                               config.APIHostInsecure = matchTrue.MatchString(value)
+                               config.APIHostInsecure = arvadosclient.StringBool(value)
                        case "ARVADOS_EXTERNAL_CLIENT":
-                               config.ExternalClient = matchTrue.MatchString(value)
+                               config.ExternalClient = arvadosclient.StringBool(value)
                        case "ARVADOS_BLOB_SIGNING_KEY":
                                blobSigningKey = value
                        }
@@ -153,7 +151,7 @@ func setupKeepClient(config apiConfig, keepServicesJSON string, blobSignatureTTL
                External: config.ExternalClient,
        }
 
-       // if keepServicesJSON is provided, use it to load services; else, use DiscoverKeepServers
+       // If keepServicesJSON is provided, use it instead of service discovery
        if keepServicesJSON == "" {
                kc, err = keepclient.MakeKeepClient(&arv)
                if err != nil {
index e49fe68616626275f00ba5bce21877dd969207dd..34d4f022bf8d7d4570e2465fd7d811dd3e1a2aa3 100644 (file)
@@ -12,6 +12,7 @@ import (
        "testing"
        "time"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
 
@@ -64,6 +65,7 @@ func (s *DoMainTestSuite) SetUpSuite(c *C) {
 func (s *DoMainTestSuite) SetUpTest(c *C) {
        logOutput := io.MultiWriter(&logBuffer)
        log.SetOutput(logOutput)
+       keepclient.RefreshServiceDiscovery()
 }
 
 func (s *DoMainTestSuite) TearDownTest(c *C) {
@@ -79,7 +81,7 @@ func setupKeepBlockCheckWithTTL(c *C, enforcePermissions bool, keepServicesJSON
        var config apiConfig
        config.APIHost = os.Getenv("ARVADOS_API_HOST")
        config.APIToken = arvadostest.DataManagerToken
-       config.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
+       config.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
 
        // Start Keep servers
        arvadostest.StartKeep(2, enforcePermissions)
@@ -89,6 +91,8 @@ func setupKeepBlockCheckWithTTL(c *C, enforcePermissions bool, keepServicesJSON
        kc, ttl, err = setupKeepClient(config, keepServicesJSON, ttl)
        c.Assert(ttl, Equals, blobSignatureTTL)
        c.Check(err, IsNil)
+
+       keepclient.RefreshServiceDiscovery()
 }
 
 // Setup test data
@@ -144,9 +148,8 @@ func setupBlockHashFile(c *C, name string, blocks []string) string {
 
 func checkErrorLog(c *C, blocks []string, prefix, suffix string) {
        for _, hash := range blocks {
-               expected := prefix + `.*` + hash + `.*` + suffix
-               match, _ := regexp.MatchString(expected, logBuffer.String())
-               c.Assert(match, Equals, true)
+               expected := `(?ms).*` + prefix + `.*` + hash + `.*` + suffix + `.*`
+               c.Check(logBuffer.String(), Matches, expected)
        }
 }
 
@@ -288,7 +291,7 @@ func (s *ServerRequiredSuite) TestLoadConfig(c *C) {
 
        c.Assert(config.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
        c.Assert(config.APIToken, Equals, arvadostest.DataManagerToken)
-       c.Assert(config.APIHostInsecure, Equals, matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")))
+       c.Assert(config.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
        c.Assert(config.ExternalClient, Equals, false)
        c.Assert(blobSigningKey, Equals, "abcdefg")
 }
index c6e7665caa2a312c327b8a603159a7da07941450..b1513a02a7e30f3211b39bbf0a4335c8dd9cf8d9 100644 (file)
@@ -6,15 +6,15 @@ import (
        "errors"
        "flag"
        "fmt"
-       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
-       "git.curoverse.com/arvados.git/sdk/go/keepclient"
        "io/ioutil"
        "log"
        "net/http"
        "os"
-       "regexp"
        "strings"
        "time"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
 )
 
 func main() {
@@ -119,8 +119,6 @@ func loadConfig(configFile string) (config apiConfig, blobSigningKey string, err
        return
 }
 
-var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
-
 // Read config from file
 func readConfigFromFile(filename string) (config apiConfig, blobSigningKey string, err error) {
        if !strings.Contains(filename, "/") {
@@ -149,9 +147,9 @@ func readConfigFromFile(filename string) (config apiConfig, blobSigningKey strin
                case "ARVADOS_API_HOST":
                        config.APIHost = value
                case "ARVADOS_API_HOST_INSECURE":
-                       config.APIHostInsecure = matchTrue.MatchString(value)
+                       config.APIHostInsecure = arvadosclient.StringBool(value)
                case "ARVADOS_EXTERNAL_CLIENT":
-                       config.ExternalClient = matchTrue.MatchString(value)
+                       config.ExternalClient = arvadosclient.StringBool(value)
                case "ARVADOS_BLOB_SIGNING_KEY":
                        blobSigningKey = value
                }
@@ -170,7 +168,7 @@ func setupKeepClient(config apiConfig, keepServicesJSON string, isDst bool, repl
                External: config.ExternalClient,
        }
 
-       // if keepServicesJSON is provided, use it to load services; else, use DiscoverKeepServers
+       // If keepServicesJSON is provided, use it instead of service discovery
        if keepServicesJSON == "" {
                kc, err = keepclient.MakeKeepClient(&arv)
                if err != nil {
index 09609eb7498bb8dc28d95bc41892f4cca9ec8563..fec1f354c957e1ca5f106f666c632af3728d3b82 100644 (file)
@@ -4,35 +4,42 @@ import (
        "crypto/md5"
        "fmt"
        "io/ioutil"
-       "log"
        "os"
        "strings"
        "testing"
        "time"
 
+       "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"
 )
 
+var kcSrc, kcDst *keepclient.KeepClient
+var srcKeepServicesJSON, dstKeepServicesJSON, blobSigningKey string
+var blobSignatureTTL = time.Duration(2*7*24) * time.Hour
+
+func resetGlobals() {
+       blobSigningKey = ""
+       srcKeepServicesJSON = ""
+       dstKeepServicesJSON = ""
+       kcSrc = nil
+       kcDst = nil
+}
+
 // Gocheck boilerplate
 func Test(t *testing.T) {
        TestingT(t)
 }
 
-// Gocheck boilerplate
 var _ = Suite(&ServerRequiredSuite{})
 var _ = Suite(&ServerNotRequiredSuite{})
 var _ = Suite(&DoMainTestSuite{})
 
-// Tests that require the Keep server running
 type ServerRequiredSuite struct{}
-type ServerNotRequiredSuite struct{}
-type DoMainTestSuite struct{}
 
 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
-       // Start API server
        arvadostest.StartAPI()
 }
 
@@ -41,36 +48,32 @@ func (s *ServerRequiredSuite) TearDownSuite(c *C) {
        arvadostest.ResetEnv()
 }
 
-var initialArgs []string
-
-func (s *DoMainTestSuite) SetUpSuite(c *C) {
-       initialArgs = os.Args
-}
-
-var kcSrc, kcDst *keepclient.KeepClient
-var srcKeepServicesJSON, dstKeepServicesJSON, blobSigningKey string
-var blobSignatureTTL = time.Duration(2*7*24) * time.Hour
-
 func (s *ServerRequiredSuite) SetUpTest(c *C) {
-       // reset all variables between tests
-       blobSigningKey = ""
-       srcKeepServicesJSON = ""
-       dstKeepServicesJSON = ""
-       kcSrc = &keepclient.KeepClient{}
-       kcDst = &keepclient.KeepClient{}
+       resetGlobals()
 }
 
 func (s *ServerRequiredSuite) TearDownTest(c *C) {
        arvadostest.StopKeep(3)
 }
 
+func (s *ServerNotRequiredSuite) SetUpTest(c *C) {
+       resetGlobals()
+}
+
+type ServerNotRequiredSuite struct{}
+
+type DoMainTestSuite struct {
+       initialArgs []string
+}
+
 func (s *DoMainTestSuite) SetUpTest(c *C) {
-       args := []string{"keep-rsync"}
-       os.Args = args
+       s.initialArgs = os.Args
+       os.Args = []string{"keep-rsync"}
+       resetGlobals()
 }
 
 func (s *DoMainTestSuite) TearDownTest(c *C) {
-       os.Args = initialArgs
+       os.Args = s.initialArgs
 }
 
 var testKeepServicesJSON = "{ \"kind\":\"arvados#keepServiceList\", \"etag\":\"\", \"self_link\":\"\", \"offset\":null, \"limit\":null, \"items\":[ { \"href\":\"/keep_services/zzzzz-bi6l4-123456789012340\", \"kind\":\"arvados#keepService\", \"etag\":\"641234567890enhj7hzx432e5\", \"uuid\":\"zzzzz-bi6l4-123456789012340\", \"owner_uuid\":\"zzzzz-tpzed-123456789012345\", \"service_host\":\"keep0.zzzzz.arvadosapi.com\", \"service_port\":25107, \"service_ssl_flag\":false, \"service_type\":\"disk\", \"read_only\":false }, { \"href\":\"/keep_services/zzzzz-bi6l4-123456789012341\", \"kind\":\"arvados#keepService\", \"etag\":\"641234567890enhj7hzx432e5\", \"uuid\":\"zzzzz-bi6l4-123456789012341\", \"owner_uuid\":\"zzzzz-tpzed-123456789012345\", \"service_host\":\"keep0.zzzzz.arvadosapi.com\", \"service_port\":25108, \"service_ssl_flag\":false, \"service_type\":\"disk\", \"read_only\":false } ], \"items_available\":2 }"
@@ -83,13 +86,13 @@ func setupRsync(c *C, enforcePermissions bool, replications int) {
        var srcConfig apiConfig
        srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
        srcConfig.APIToken = arvadostest.DataManagerToken
-       srcConfig.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
+       srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
 
        // dstConfig
        var dstConfig apiConfig
        dstConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
        dstConfig.APIToken = arvadostest.DataManagerToken
-       dstConfig.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
+       dstConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
 
        if enforcePermissions {
                blobSigningKey = arvadostest.BlobSigningKey
@@ -97,45 +100,30 @@ func setupRsync(c *C, enforcePermissions bool, replications int) {
 
        // Start Keep servers
        arvadostest.StartKeep(3, enforcePermissions)
+       keepclient.RefreshServiceDiscovery()
 
        // setup keepclients
        var err error
        kcSrc, _, err = setupKeepClient(srcConfig, srcKeepServicesJSON, false, 0, blobSignatureTTL)
-       c.Check(err, IsNil)
+       c.Assert(err, IsNil)
 
        kcDst, _, err = setupKeepClient(dstConfig, dstKeepServicesJSON, true, replications, 0)
-       c.Check(err, IsNil)
+       c.Assert(err, IsNil)
 
-       for uuid := range kcSrc.LocalRoots() {
+       srcRoots := map[string]string{}
+       dstRoots := map[string]string{}
+       for uuid, root := range kcSrc.LocalRoots() {
                if strings.HasSuffix(uuid, "02") {
-                       delete(kcSrc.LocalRoots(), uuid)
+                       dstRoots[uuid] = root
+               } else {
+                       srcRoots[uuid] = root
                }
        }
-       for uuid := range kcSrc.GatewayRoots() {
-               if strings.HasSuffix(uuid, "02") {
-                       delete(kcSrc.GatewayRoots(), uuid)
-               }
+       if srcKeepServicesJSON == "" {
+               kcSrc.SetServiceRoots(srcRoots, srcRoots, srcRoots)
        }
-       for uuid := range kcSrc.WritableLocalRoots() {
-               if strings.HasSuffix(uuid, "02") {
-                       delete(kcSrc.WritableLocalRoots(), uuid)
-               }
-       }
-
-       for uuid := range kcDst.LocalRoots() {
-               if strings.HasSuffix(uuid, "00") || strings.HasSuffix(uuid, "01") {
-                       delete(kcDst.LocalRoots(), uuid)
-               }
-       }
-       for uuid := range kcDst.GatewayRoots() {
-               if strings.HasSuffix(uuid, "00") || strings.HasSuffix(uuid, "01") {
-                       delete(kcDst.GatewayRoots(), uuid)
-               }
-       }
-       for uuid := range kcDst.WritableLocalRoots() {
-               if strings.HasSuffix(uuid, "00") || strings.HasSuffix(uuid, "01") {
-                       delete(kcDst.WritableLocalRoots(), uuid)
-               }
+       if dstKeepServicesJSON == "" {
+               kcDst.SetServiceRoots(dstRoots, dstRoots, dstRoots)
        }
 
        if replications == 0 {
@@ -188,22 +176,8 @@ func (s *ServerRequiredSuite) TestRsyncInitializeWithKeepServicesJSON(c *C) {
 
        localRoots := kcSrc.LocalRoots()
        c.Check(localRoots, NotNil)
-
-       foundIt := false
-       for k := range localRoots {
-               if k == "zzzzz-bi6l4-123456789012340" {
-                       foundIt = true
-               }
-       }
-       c.Check(foundIt, Equals, true)
-
-       foundIt = false
-       for k := range localRoots {
-               if k == "zzzzz-bi6l4-123456789012341" {
-                       foundIt = true
-               }
-       }
-       c.Check(foundIt, Equals, true)
+       c.Check(localRoots["zzzzz-bi6l4-123456789012340"], Not(Equals), "")
+       c.Check(localRoots["zzzzz-bi6l4-123456789012341"], Not(Equals), "")
 }
 
 // Test keep-rsync initialization with default replications count
@@ -329,8 +303,8 @@ func (s *ServerRequiredSuite) TestErrorDuringRsync_FakeSrcKeepservers(c *C) {
        setupRsync(c, false, 1)
 
        err := performKeepRsync(kcSrc, kcDst, blobSignatureTTL, "", "")
-       log.Printf("Err = %v", err)
-       c.Check(strings.Contains(err.Error(), "no such host"), Equals, true)
+       c.Assert(err, NotNil)
+       c.Check(err.Error(), Matches, ".*no such host.*")
 }
 
 // Setup rsync using dstKeepServicesJSON with fake keepservers.
@@ -341,8 +315,8 @@ func (s *ServerRequiredSuite) TestErrorDuringRsync_FakeDstKeepservers(c *C) {
        setupRsync(c, false, 1)
 
        err := performKeepRsync(kcSrc, kcDst, blobSignatureTTL, "", "")
-       log.Printf("Err = %v", err)
-       c.Check(strings.Contains(err.Error(), "no such host"), Equals, true)
+       c.Assert(err, NotNil)
+       c.Check(err.Error(), Matches, ".*no such host.*")
 }
 
 // Test rsync with signature error during Get from src.
@@ -356,7 +330,8 @@ func (s *ServerRequiredSuite) TestErrorDuringRsync_ErrorGettingBlockFromSrc(c *C
        blobSigningKey = "thisisfakeblobsigningkey"
 
        err := performKeepRsync(kcSrc, kcDst, blobSignatureTTL, blobSigningKey, "")
-       c.Check(strings.Contains(err.Error(), "HTTP 403 \"Forbidden\""), Equals, true)
+       c.Assert(err, NotNil)
+       c.Check(err.Error(), Matches, ".*HTTP 403 \"Forbidden\".*")
 }
 
 // Test rsync with error during Put to src.
@@ -370,7 +345,8 @@ func (s *ServerRequiredSuite) TestErrorDuringRsync_ErrorPuttingBlockInDst(c *C)
        kcDst.Want_replicas = 2
 
        err := performKeepRsync(kcSrc, kcDst, blobSignatureTTL, blobSigningKey, "")
-       c.Check(strings.Contains(err.Error(), "Could not write sufficient replicas"), Equals, true)
+       c.Assert(err, NotNil)
+       c.Check(err.Error(), Matches, ".*Could not write sufficient replicas.*")
 }
 
 // Test loadConfig func
@@ -391,7 +367,7 @@ func (s *ServerNotRequiredSuite) TestLoadConfig(c *C) {
 
        c.Assert(srcConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
        c.Assert(srcConfig.APIToken, Equals, arvadostest.DataManagerToken)
-       c.Assert(srcConfig.APIHostInsecure, Equals, matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")))
+       c.Assert(srcConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
        c.Assert(srcConfig.ExternalClient, Equals, false)
 
        dstConfig, _, err := loadConfig(dstConfigFile)
@@ -399,7 +375,7 @@ func (s *ServerNotRequiredSuite) TestLoadConfig(c *C) {
 
        c.Assert(dstConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
        c.Assert(dstConfig.APIToken, Equals, arvadostest.DataManagerToken)
-       c.Assert(dstConfig.APIHostInsecure, Equals, matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")))
+       c.Assert(dstConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
        c.Assert(dstConfig.ExternalClient, Equals, false)
 
        c.Assert(srcBlobSigningKey, Equals, "abcdefg")
@@ -414,15 +390,15 @@ func (s *ServerNotRequiredSuite) TestLoadConfig_MissingSrcConfig(c *C) {
 // Test loadConfig func - error reading config
 func (s *ServerNotRequiredSuite) TestLoadConfig_ErrorLoadingSrcConfig(c *C) {
        _, _, err := loadConfig("no-such-config-file")
-       c.Assert(strings.Contains(err.Error(), "no such file or directory"), Equals, true)
+       c.Assert(err, NotNil)
+       c.Check(err.Error(), Matches, ".*no such file or directory.*")
 }
 
 func (s *ServerNotRequiredSuite) TestSetupKeepClient_NoBlobSignatureTTL(c *C) {
        var srcConfig apiConfig
        srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
        srcConfig.APIToken = arvadostest.DataManagerToken
-       srcConfig.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
-       arvadostest.StartKeep(2, false)
+       srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
 
        _, ttl, err := setupKeepClient(srcConfig, srcKeepServicesJSON, false, 0, 0)
        c.Check(err, IsNil)
@@ -448,7 +424,7 @@ func setupConfigFile(c *C, name string) *os.File {
 
 func (s *DoMainTestSuite) Test_doMain_NoSrcConfig(c *C) {
        err := doMain()
-       c.Check(err, NotNil)
+       c.Assert(err, NotNil)
        c.Assert(err.Error(), Equals, "Error loading src configuration from file: config file not specified")
 }
 
@@ -457,7 +433,7 @@ func (s *DoMainTestSuite) Test_doMain_SrcButNoDstConfig(c *C) {
        args := []string{"-replications", "3", "-src", srcConfig.Name()}
        os.Args = append(os.Args, args...)
        err := doMain()
-       c.Check(err, NotNil)
+       c.Assert(err, NotNil)
        c.Assert(err.Error(), Equals, "Error loading dst configuration from file: config file not specified")
 }
 
@@ -465,8 +441,8 @@ func (s *DoMainTestSuite) Test_doMain_BadSrcConfig(c *C) {
        args := []string{"-src", "abcd"}
        os.Args = append(os.Args, args...)
        err := doMain()
-       c.Check(err, NotNil)
-       c.Assert(strings.HasPrefix(err.Error(), "Error loading src configuration from file: Error reading config file"), Equals, true)
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Matches, "Error loading src configuration from file: Error reading config file.*")
 }
 
 func (s *DoMainTestSuite) Test_doMain_WithReplicationsButNoSrcConfig(c *C) {
@@ -488,6 +464,7 @@ func (s *DoMainTestSuite) Test_doMainWithSrcAndDstConfig(c *C) {
        // actual copying to dst will happen, but that's ok.
        arvadostest.StartKeep(2, false)
        defer arvadostest.StopKeep(2)
+       keepclient.RefreshServiceDiscovery()
 
        err := doMain()
        c.Check(err, IsNil)