Merge branch '5011-arv-put-replication' closes #5011
authorTom Clegg <tom@curoverse.com>
Fri, 6 Feb 2015 22:53:33 +0000 (17:53 -0500)
committerTom Clegg <tom@curoverse.com>
Fri, 6 Feb 2015 22:53:33 +0000 (17:53 -0500)
22 files changed:
.gitignore
apps/workbench/.gitignore
apps/workbench/test/test_helper.rb
sdk/go/arvadosclient/arvadosclient.go
sdk/go/arvadosclient/arvadosclient_test.go
sdk/go/arvadostest/run_servers.go [new file with mode: 0644]
sdk/go/keepclient/keepclient_test.go
sdk/go/keepclient/support.go
sdk/python/arvados/errors.py
sdk/python/arvados/events.py
sdk/python/tests/run_test_server.py
sdk/python/tests/test_api.py
sdk/python/tests/test_keep_client.py
sdk/python/tests/test_pipeline_template.py
sdk/python/tests/test_websockets.py
services/api/.gitignore
services/api/Gemfile
services/api/config/application.default.yml
services/api/test/websocket_runner.rb
services/fuse/arvados_fuse/__init__.py
services/fuse/tests/test_mount.py
services/keepproxy/keepproxy_test.go

index 8cc6b89324311d0cd7db51f9c6a5e7ba400253dc..eec475862e6ec2a87554e0fca90697e87f441bf5 100644 (file)
@@ -10,10 +10,9 @@ sdk/perl/MYMETA.*
 sdk/perl/Makefile
 sdk/perl/blib
 sdk/perl/pm_to_blib
-*/vendor/bundle
+*/vendor
+*/*/vendor
 sdk/java/target
 *.class
-apps/workbench/vendor/bundle
-services/api/vendor/bundle
 sdk/java/log
-sdk/cli/vendor
+/tmp
index 24a7a84a31249c9c69894ce9dd3ecb5b7fe7446c..9bef02bbfda670595750fd99a4461005ce5b8f12 100644 (file)
@@ -3,6 +3,7 @@
 
 # Ignore all logfiles and tempfiles.
 /log/*.log
+/log/*.log.gz
 /tmp
 
 /config/.secret_token
index 7c454c9877b2bbb4c97c417cf5406cf433924ca3..078190bd32d0a02af56b59df5d28ad1556a511d8 100644 (file)
@@ -36,13 +36,17 @@ class ActiveSupport::TestCase
     Thread.current[:arvados_api_token] = auth['api_token']
   end
 
-  teardown do
+  setup do
     Thread.current[:arvados_api_token] = nil
     Thread.current[:user] = nil
     Thread.current[:reader_tokens] = nil
     # Diagnostics suite doesn't run a server, so there's no cache to clear.
     Rails.cache.clear unless (Rails.env == "diagnostics")
     # Restore configuration settings changed during tests
+    self.class.reset_application_config
+  end
+
+  def self.reset_application_config
     $application_config.each do |k,v|
       if k.match /^[^.]*$/
         Rails.configuration.send (k + '='), v
@@ -95,99 +99,73 @@ class ActiveSupport::TestCase
 end
 
 class ApiServerForTests
+  PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
-  SERVER_PID_PATH = File.expand_path('tmp/pids/wbtest-server.pid', ARV_API_SERVER_DIR)
-  WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/wstest-server.pid', ARV_API_SERVER_DIR)
+  SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
+  WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
   @main_process_pid = $$
+  @@server_is_running = false
 
-  def _system(*cmd)
-    $stderr.puts "_system #{cmd.inspect}"
+  def check_call *args
+    output = nil
     Bundler.with_clean_env do
-      if not system({'RAILS_ENV' => 'test', "ARVADOS_WEBSOCKETS" => (if @websocket then "ws-only" end)}, *cmd)
-        raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}"
+      output = IO.popen *args do |io|
+        io.read
+      end
+      if not $?.success?
+        raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
       end
     end
+    output
   end
 
-  def make_ssl_cert
-    unless File.exists? './self-signed.key'
-      _system('openssl', 'req', '-new', '-x509', '-nodes',
-              '-out', './self-signed.pem',
-              '-keyout', './self-signed.key',
-              '-days', '3650',
-              '-subj', '/CN=localhost')
+  def run_test_server
+    env_script = nil
+    Dir.chdir PYTHON_TESTS_DIR do
+      env_script = check_call %w(python ./run_test_server.py start --auth admin)
     end
-  end
-
-  def kill_server
-    if (pid = find_server_pid)
-      $stderr.puts "Sending TERM to API server, pid #{pid}"
-      Process.kill 'TERM', pid
+    test_env = {}
+    env_script.each_line do |line|
+      line = line.chomp
+      if 0 == line.index('export ')
+        toks = line.sub('export ', '').split '=', 2
+        $stderr.puts "run_test_server.py: #{toks[0]}=#{toks[1]}"
+        test_env[toks[0]] = toks[1]
+      end
     end
+    test_env
   end
 
-  def find_server_pid
-    pid = nil
-    begin
-      pid = IO.read(@pidfile).to_i
-      $stderr.puts "API server is running, pid #{pid.inspect}"
-    rescue Errno::ENOENT
+  def stop_test_server
+    Dir.chdir PYTHON_TESTS_DIR do
+      # This is a no-op if we're running within run-tests.sh
+      check_call %w(python ./run_test_server.py stop)
     end
-    return pid
+    @@server_is_running = false
   end
 
-  def run(args=[])
+  def run args=[]
+    return if @@server_is_running
+
+    # Stop server left over from interrupted previous run
+    stop_test_server
+
     ::MiniTest.after_run do
-      self.kill_server
+      stop_test_server
     end
 
-    @websocket = args.include?("--websockets")
-
-    @pidfile = if @websocket
-                 WEBSOCKET_PID_PATH
-               else
-                 SERVER_PID_PATH
-               end
-
-    # Kill server left over from previous test run
-    self.kill_server
-
-    Capybara.javascript_driver = :poltergeist
-    Dir.chdir(ARV_API_SERVER_DIR) do |apidir|
-      ENV["NO_COVERAGE_TEST"] = "1"
-      if @websocket
-        _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3333',
-                '--pid-file', @pidfile)
-      else
-        make_ssl_cert
-        if ENV['ARVADOS_TEST_API_INSTALLED'].blank?
-          _system('bundle', 'exec', 'rake', 'db:test:load')
-          _system('bundle', 'exec', 'rake', 'db:fixtures:load')
-        end
-        _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3000',
-                '--pid-file', @pidfile,
-                '--ssl',
-                '--ssl-certificate', 'self-signed.pem',
-                '--ssl-certificate-key', 'self-signed.key')
-      end
-      timeout = Time.now.tv_sec + 10
-      good_pid = false
-      while (not good_pid) and (Time.now.tv_sec < timeout)
-        sleep 0.2
-        server_pid = find_server_pid
-        good_pid = (server_pid and
-                    (server_pid > 0) and
-                    (Process.kill(0, server_pid) rescue false))
-      end
-      if not good_pid
-        raise RuntimeError, "could not find API server Rails pid"
-      end
-    end
+    test_env = run_test_server
+    $application_config['arvados_login_base'] = "https://#{test_env['ARVADOS_API_HOST']}/login"
+    $application_config['arvados_v1_base'] = "https://#{test_env['ARVADOS_API_HOST']}/arvados/v1"
+    $application_config['arvados_insecure_host'] = true
+    ActiveSupport::TestCase.reset_application_config
+
+    @@server_is_running = true
   end
 
-  def run_rake_task(task_name, arg_string)
-    Dir.chdir(ARV_API_SERVER_DIR) do
-      _system('bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]")
+  def run_rake_task task_name, arg_string
+    Dir.chdir ARV_API_SERVER_DIR do
+      check_call ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
     end
   end
 end
index 5ea2524aa63c730632c567edb10132224b919b3a..7c2442653c71494edcb6f8e06cbff533d9890fd1 100644 (file)
@@ -64,10 +64,10 @@ func MakeArvadosClient() (kc ArvadosClient, err error) {
                        TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}},
                External: external}
 
-       if os.Getenv("ARVADOS_API_HOST") == "" {
+       if kc.ApiServer == "" {
                return kc, MissingArvadosApiHost
        }
-       if os.Getenv("ARVADOS_API_TOKEN") == "" {
+       if kc.ApiToken == "" {
                return kc, MissingArvadosApiToken
        }
 
index bf9b4e31c41dbb5ba974e1da3b4d1dbb99cda7e3..1af964d0a045ad2b4bb0a6dd9610fcf11d8027d3 100644 (file)
@@ -1,11 +1,10 @@
 package arvadosclient
 
 import (
-       "fmt"
        . "gopkg.in/check.v1"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "net/http"
        "os"
-       "os/exec"
        "testing"
 )
 
@@ -19,47 +18,35 @@ var _ = Suite(&ServerRequiredSuite{})
 // Tests that require the Keep server running
 type ServerRequiredSuite struct{}
 
-func pythonDir() string {
-       cwd, _ := os.Getwd()
-       return fmt.Sprintf("%s/../../python/tests", cwd)
+func (s *ServerRequiredSuite) SetUpSuite(c *C) {
+       arvadostest.StartAPI()
+       arvadostest.StartKeep()
 }
 
-func (s *ServerRequiredSuite) SetUpSuite(c *C) {
-       os.Chdir(pythonDir())
-       if err := exec.Command("python", "run_test_server.py", "start").Run(); err != nil {
-               panic("'python run_test_server.py start' returned error")
-       }
-       if err := exec.Command("python", "run_test_server.py", "start_keep").Run(); err != nil {
-               panic("'python run_test_server.py start_keep' returned error")
-       }
+func (s *ServerRequiredSuite) SetUpTest(c *C) {
+       arvadostest.ResetEnv()
 }
 
-func (s *ServerRequiredSuite) TestMakeArvadosClient(c *C) {
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
+func (s *ServerRequiredSuite) TestMakeArvadosClientSecure(c *C) {
        os.Setenv("ARVADOS_API_HOST_INSECURE", "")
-
        kc, err := MakeArvadosClient()
-       c.Check(kc.ApiServer, Equals, "localhost:3000")
-       c.Check(kc.ApiToken, Equals, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
+       c.Assert(err, Equals, nil)
+       c.Check(kc.ApiServer, Equals, os.Getenv("ARVADOS_API_HOST"))
+       c.Check(kc.ApiToken, Equals, os.Getenv("ARVADOS_API_TOKEN"))
        c.Check(kc.ApiInsecure, Equals, false)
+}
 
+func (s *ServerRequiredSuite) TestMakeArvadosClientInsecure(c *C) {
        os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
-
-       kc, err = MakeArvadosClient()
-       c.Check(kc.ApiServer, Equals, "localhost:3000")
-       c.Check(kc.ApiToken, Equals, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
+       kc, err := MakeArvadosClient()
+       c.Assert(err, Equals, nil)
        c.Check(kc.ApiInsecure, Equals, true)
+       c.Check(kc.ApiServer, Equals, os.Getenv("ARVADOS_API_HOST"))
+       c.Check(kc.ApiToken, Equals, os.Getenv("ARVADOS_API_TOKEN"))
        c.Check(kc.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify, Equals, true)
-
-       c.Assert(err, Equals, nil)
 }
 
 func (s *ServerRequiredSuite) TestCreatePipelineTemplate(c *C) {
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
-
        arv, err := MakeArvadosClient()
 
        getback := make(Dict)
@@ -91,10 +78,6 @@ func (s *ServerRequiredSuite) TestCreatePipelineTemplate(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestErrorResponse(c *C) {
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
-
        arv, _ := MakeArvadosClient()
 
        getback := make(Dict)
diff --git a/sdk/go/arvadostest/run_servers.go b/sdk/go/arvadostest/run_servers.go
new file mode 100644 (file)
index 0000000..cad1691
--- /dev/null
@@ -0,0 +1,123 @@
+package arvadostest
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "os/exec"
+       "strings"
+)
+
+var authSettings = make(map[string]string)
+
+func ResetEnv() {
+       for k, v := range authSettings {
+               os.Setenv(k, v)
+       }
+}
+
+func ParseAuthSettings(authScript []byte) {
+       scanner := bufio.NewScanner(bytes.NewReader(authScript))
+       for scanner.Scan() {
+               line := scanner.Text()
+               if 0 != strings.Index(line, "export ") {
+                       log.Printf("Ignoring: %v", line)
+                       continue
+               }
+               toks := strings.SplitN(strings.Replace(line, "export ", "", 1), "=", 2)
+               if len(toks) == 2 {
+                       authSettings[toks[0]] = toks[1]
+               } else {
+                       log.Fatalf("Could not parse: %v", line)
+               }
+       }
+       log.Printf("authSettings: %v", authSettings)
+}
+
+var pythonTestDir string = ""
+
+func chdirToPythonTests() {
+       if pythonTestDir != "" {
+               if err := os.Chdir(pythonTestDir); err != nil {
+                       log.Fatalf("chdir %s: %s", pythonTestDir, err)
+               }
+               return
+       }
+       for {
+               if err := os.Chdir("sdk/python/tests"); err == nil {
+                       pythonTestDir, err = os.Getwd()
+                       return
+               }
+               if parent, err := os.Getwd(); err != nil || parent == "/" {
+                       log.Fatalf("sdk/python/tests/ not found in any ancestor")
+               }
+               if err := os.Chdir(".."); err != nil {
+                       log.Fatal(err)
+               }
+       }
+}
+
+func StartAPI() {
+       cwd, _ := os.Getwd()
+       defer os.Chdir(cwd)
+       chdirToPythonTests()
+
+       cmd := exec.Command("python", "run_test_server.py", "start", "--auth", "admin")
+       stderr, err := cmd.StderrPipe()
+       if err != nil {
+               log.Fatal(err)
+       }
+       go io.Copy(os.Stderr, stderr)
+       stdout, err := cmd.StdoutPipe()
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err = cmd.Start(); err != nil {
+               log.Fatal(err)
+       }
+       var authScript []byte
+       if authScript, err = ioutil.ReadAll(stdout); err != nil {
+               log.Fatal(err)
+       }
+       if err = cmd.Wait(); err != nil {
+               log.Fatal(err)
+       }
+       ParseAuthSettings(authScript)
+       ResetEnv()
+}
+
+func StopAPI() {
+       cwd, _ := os.Getwd()
+       defer os.Chdir(cwd)
+       chdirToPythonTests()
+
+       exec.Command("python", "run_test_server.py", "stop").Run()
+}
+
+func StartKeep() {
+       cwd, _ := os.Getwd()
+       defer os.Chdir(cwd)
+       chdirToPythonTests()
+
+       cmd := exec.Command("python", "run_test_server.py", "start_keep")
+       stderr, err := cmd.StderrPipe()
+       if err != nil {
+               log.Fatalf("Setting up stderr pipe: %s", err)
+       }
+       go io.Copy(os.Stderr, stderr)
+       if err := cmd.Run(); err != nil {
+               panic(fmt.Sprintf("'python run_test_server.py start_keep' returned error %s", err))
+       }
+}
+
+func StopKeep() {
+       cwd, _ := os.Getwd()
+       defer os.Chdir(cwd)
+       chdirToPythonTests()
+
+       exec.Command("python", "run_test_server.py", "stop_keep").Run()
+}
index 8487e00786d93d4acece1fcf83c065629e499944..cbd27d72e7c7e9310de1ed027e47912b7a187baa 100644 (file)
@@ -5,6 +5,7 @@ import (
        "flag"
        "fmt"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/streamer"
        . "gopkg.in/check.v1"
        "io"
@@ -13,7 +14,6 @@ import (
        "net"
        "net/http"
        "os"
-       "os/exec"
        "testing"
 )
 
@@ -44,42 +44,19 @@ func (s *ServerRequiredSuite) SetUpSuite(c *C) {
                c.Skip("Skipping tests that require server")
                return
        }
-       os.Chdir(pythonDir())
-       {
-               cmd := exec.Command("python", "run_test_server.py", "start")
-               stderr, err := cmd.StderrPipe()
-               if err != nil {
-                       log.Fatalf("Setting up stderr pipe: %s", err)
-               }
-               go io.Copy(os.Stderr, stderr)
-               if err := cmd.Run(); err != nil {
-                       panic(fmt.Sprintf("'python run_test_server.py start' returned error %s", err))
-               }
-       }
-       {
-               cmd := exec.Command("python", "run_test_server.py", "start_keep")
-               stderr, err := cmd.StderrPipe()
-               if err != nil {
-                       log.Fatalf("Setting up stderr pipe: %s", err)
-               }
-               go io.Copy(os.Stderr, stderr)
-               if err := cmd.Run(); err != nil {
-                       panic(fmt.Sprintf("'python run_test_server.py start_keep' returned error %s", err))
-               }
-       }
+       arvadostest.StartAPI()
+       arvadostest.StartKeep()
 }
 
 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
-       os.Chdir(pythonDir())
-       exec.Command("python", "run_test_server.py", "stop_keep").Run()
-       exec.Command("python", "run_test_server.py", "stop").Run()
+       if *no_server {
+               return
+       }
+       arvadostest.StopKeep()
+       arvadostest.StopAPI()
 }
 
 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
-
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, Equals, nil)
 
@@ -88,7 +65,7 @@ func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
        c.Assert(err, Equals, nil)
        c.Check(len(kc.ServiceRoots()), Equals, 2)
        for _, root := range kc.ServiceRoots() {
-               c.Check(root, Matches, "http://localhost:2510[\\d]")
+               c.Check(root, Matches, "http://localhost:\\d+")
        }
 }
 
@@ -600,9 +577,6 @@ func (s *StandaloneSuite) TestGetWithFailures(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
        content := []byte("TestPutGetHead")
 
        arv, err := arvadosclient.MakeArvadosClient()
@@ -626,7 +600,7 @@ func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
                r, n, url2, err := kc.Get(hash)
                c.Check(err, Equals, nil)
                c.Check(n, Equals, int64(len(content)))
-               c.Check(url2, Equals, fmt.Sprintf("http://localhost:25108/%s", hash))
+               c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
 
                read_content, err2 := ioutil.ReadAll(r)
                c.Check(err2, Equals, nil)
@@ -636,7 +610,7 @@ func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
                n, url2, err := kc.Ask(hash)
                c.Check(err, Equals, nil)
                c.Check(n, Equals, int64(len(content)))
-               c.Check(url2, Equals, fmt.Sprintf("http://localhost:25108/%s", hash))
+               c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
        }
 }
 
index c24849e687a8d11cf2e5d2154fdd62d0e470ec83..9db6ebcbaa234a82778825e92baa8a7017a3f418 100644 (file)
@@ -11,7 +11,6 @@ import (
        "log"
        "net"
        "net/http"
-       "os"
        "strings"
        "time"
 )
@@ -78,14 +77,6 @@ func (this *KeepClient) setClientSettingsStore() {
 }
 
 func (this *KeepClient) DiscoverKeepServers() error {
-       if prx := os.Getenv("ARVADOS_KEEP_PROXY"); prx != "" {
-               sr := map[string]string{"proxy": prx}
-               this.SetServiceRoots(sr)
-               this.Using_proxy = true
-               this.setClientSettingsProxy()
-               return nil
-       }
-
        type svcList struct {
                Items []keepDisk `json:"items"`
        }
index f70fa17149711d1d869af4efa104d638b8d64ebc..16f4096572c422cf37a93a7110b19c45aa8f1061 100644 (file)
@@ -74,3 +74,5 @@ class NoKeepServersError(Exception):
     pass
 class StaleWriterStateError(Exception):
     pass
+class FeatureNotEnabledError(Exception):
+    pass
index fc86cb46d4eecfbdd821f0e6f34da5df2061d437..268692637afb7a45721322f1617b3aea50788d60 100644 (file)
@@ -1,13 +1,15 @@
-from ws4py.client.threadedclient import WebSocketClient
-import threading
+import arvados
+import config
+import errors
+
+import logging
 import json
-import os
+import threading
 import time
-import ssl
+import os
 import re
-import config
-import logging
-import arvados
+import ssl
+from ws4py.client.threadedclient import WebSocketClient
 
 _logger = logging.getLogger('arvados.events')
 
@@ -22,6 +24,11 @@ class EventClient(WebSocketClient):
             ssl_options['cert_reqs'] = ssl.CERT_NONE
         else:
             ssl_options['cert_reqs'] = ssl.CERT_REQUIRED
+
+        # Warning: If the host part of url resolves to both IPv6 and
+        # IPv4 addresses (common with "localhost"), only one of them
+        # will be attempted -- and it might not be the right one. See
+        # ws4py's WebSocketBaseClient.__init__.
         super(EventClient, self).__init__(url, ssl_options=ssl_options)
         self.filters = filters
         self.on_event = on_event
@@ -105,6 +112,22 @@ class PollClient(threading.Thread):
         del self.filters[self.filters.index(filters)]
 
 
+def _subscribe_websocket(api, filters, on_event):
+    endpoint = api._rootDesc.get('websocketUrl', None)
+    if not endpoint:
+        raise errors.FeatureNotEnabledError(
+            "Server does not advertise a websocket endpoint")
+    uri_with_token = "{}?api_token={}".format(endpoint, api.api_token)
+    client = EventClient(uri_with_token, filters, on_event)
+    ok = False
+    try:
+        client.connect()
+        ok = True
+        return client
+    finally:
+        if not ok:
+            client.close_connection()
+
 def subscribe(api, filters, on_event, poll_fallback=15):
     '''
     api: a client object retrieved from arvados.api(). The caller should not use this client object for anything else after calling subscribe().
@@ -112,22 +135,13 @@ def subscribe(api, filters, on_event, poll_fallback=15):
     on_event: The callback when a message is received.
     poll_fallback: If websockets are not available, fall back to polling every N seconds.  If poll_fallback=False, this will return None if websockets are not available.
     '''
-    ws = None
-    if 'websocketUrl' in api._rootDesc:
-        try:
-            url = "{}?api_token={}".format(api._rootDesc['websocketUrl'], api.api_token)
-            ws = EventClient(url, filters, on_event)
-            ws.connect()
-            return ws
-        except Exception as e:
-            _logger.warn("Got exception %s trying to connect to websockets at %s" % (e, api._rootDesc['websocketUrl']))
-            if ws:
-                ws.close_connection()
-    if poll_fallback:
-        _logger.warn("Websockets not available, falling back to log table polling")
-        p = PollClient(api, filters, on_event, poll_fallback)
-        p.start()
-        return p
-    else:
-        _logger.error("Websockets not available")
-        return None
+    if not poll_fallback:
+        return _subscribe_websocket(api, filters, on_event)
+
+    try:
+        return _subscribe_websocket(api, filters, on_event)
+    except Exception as e:
+        _logger.warn("Falling back to polling after websocket error: %s" % e)
+    p = PollClient(api, filters, on_event, poll_fallback)
+    p.start()
+    return p
index 739c75499509fabb24312bdd6c87a9cb5d8f48e8..5fee9eb0ec193d0cd4c3496480bf8b103727ea80 100644 (file)
@@ -1,10 +1,17 @@
 #!/usr/bin/env python
 
 import argparse
+import atexit
+import httplib2
 import os
+import pipes
+import random
+import re
 import shutil
 import signal
+import socket
 import subprocess
+import string
 import sys
 import tempfile
 import time
@@ -21,18 +28,19 @@ if __name__ == '__main__' and os.path.exists(
 import arvados.api
 import arvados.config
 
-SERVICES_SRC_DIR = os.path.join(MY_DIRNAME, '../../../services')
-SERVER_PID_PATH = 'tmp/pids/webrick-test.pid'
-WEBSOCKETS_SERVER_PID_PATH = 'tmp/pids/passenger-test.pid'
+ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
+SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
+SERVER_PID_PATH = 'tmp/pids/test-server.pid'
 if 'GOPATH' in os.environ:
     gopaths = os.environ['GOPATH'].split(':')
     gobins = [os.path.join(path, 'bin') for path in gopaths]
     os.environ['PATH'] = ':'.join(gobins) + ':' + os.environ['PATH']
 
-if os.path.isdir('tests'):
-    TEST_TMPDIR = 'tests/tmp'
-else:
-    TEST_TMPDIR = 'tmp'
+TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
+if not os.path.exists(TEST_TMPDIR):
+    os.mkdir(TEST_TMPDIR)
+
+my_api_host = None
 
 def find_server_pid(PID_PATH, wait=10):
     now = time.time()
@@ -55,90 +63,201 @@ def find_server_pid(PID_PATH, wait=10):
 
     return server_pid
 
-def kill_server_pid(PID_PATH, wait=10):
+def kill_server_pid(pidfile, wait=10, passenger_root=False):
+    # Must re-import modules in order to work during atexit
+    import os
+    import signal
+    import subprocess
+    import time
     try:
+        if passenger_root:
+            # First try to shut down nicely
+            restore_cwd = os.getcwd()
+            os.chdir(passenger_root)
+            subprocess.call([
+                'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
+            os.chdir(restore_cwd)
         now = time.time()
         timeout = now + wait
-        with open(PID_PATH, 'r') as f:
+        with open(pidfile, 'r') as f:
             server_pid = int(f.read())
         while now <= timeout:
-            os.kill(server_pid, signal.SIGTERM)
-            os.getpgid(server_pid) # throw OSError if no such pid
-            now = time.time()
+            if not passenger_root or timeout - now < wait / 2:
+                # Half timeout has elapsed. Start sending SIGTERM
+                os.kill(server_pid, signal.SIGTERM)
+            # Raise OSError if process has disappeared
+            os.getpgid(server_pid)
             time.sleep(0.1)
+            now = time.time()
     except IOError:
-        good_pid = False
+        pass
     except OSError:
-        good_pid = False
-
-def run(websockets=False, reuse_server=False):
-    cwd = os.getcwd()
-    os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
-
-    if websockets:
-        pid_file = WEBSOCKETS_SERVER_PID_PATH
-    else:
-        pid_file = SERVER_PID_PATH
-
-    test_pid = find_server_pid(pid_file, 0)
-
-    if test_pid is None or not reuse_server:
-        # do not try to run both server variants at once
-        stop()
-
-        # delete cached discovery document
-        shutil.rmtree(arvados.http_cache('discovery'))
-
-        # Setup database
-        os.environ["RAILS_ENV"] = "test"
-        subprocess.call(['bundle', 'exec', 'rake', 'tmp:cache:clear'])
-        subprocess.call(['bundle', 'exec', 'rake', 'db:test:load'])
-        subprocess.call(['bundle', 'exec', 'rake', 'db:fixtures:load'])
+        pass
 
-        subprocess.call(['bundle', 'exec', 'rails', 'server', '-d',
-                         '--pid',
-                         os.path.join(os.getcwd(), SERVER_PID_PATH),
-                         '-p3000'])
-        os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3000"
+def find_available_port():
+    """Return an IPv4 port number that is not in use right now.
 
-        if websockets:
-            os.environ["ARVADOS_WEBSOCKETS"] = "ws-only"
-            subprocess.call(['bundle', 'exec',
-                             'passenger', 'start', '-d', '-p3333',
-                             '--pid-file',
-                             os.path.join(os.getcwd(), WEBSOCKETS_SERVER_PID_PATH)
-                         ])
+    We assume whoever needs to use the returned port is able to reuse
+    a recently used port without waiting for TIME_WAIT (see
+    SO_REUSEADDR / SO_REUSEPORT).
 
-        pid = find_server_pid(SERVER_PID_PATH)
+    Some opportunity for races here, but it's better than choosing
+    something at random and not checking at all. If all of our servers
+    (hey Passenger) knew that listening on port 0 was a thing, the OS
+    would take care of the races, and this wouldn't be needed at all.
+    """
 
-    os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
-    os.environ["ARVADOS_API_TOKEN"] = ""
-    os.chdir(cwd)
+    sock = socket.socket()
+    sock.bind(('0.0.0.0', 0))
+    port = sock.getsockname()[1]
+    sock.close()
+    return port
 
-def stop():
-    cwd = os.getcwd()
-    os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
+def run(leave_running_atexit=False):
+    """Ensure an API server is running, and ARVADOS_API_* env vars have
+    admin credentials for it.
 
-    kill_server_pid(WEBSOCKETS_SERVER_PID_PATH, 0)
-    kill_server_pid(SERVER_PID_PATH, 0)
+    If ARVADOS_TEST_API_HOST is set, a parent process has started a
+    test server for us to use: we just need to reset() it using the
+    admin token fixture.
 
-    try:
-        os.unlink('self-signed.pem')
-    except:
-        pass
-
-    try:
-        os.unlink('self-signed.key')
-    except:
-        pass
+    If a previous call to run() started a new server process, and it
+    is still running, we just need to reset() it to fixture state and
+    return.
 
-    os.chdir(cwd)
+    If neither of those options work out, we'll really start a new
+    server.
+    """
+    global my_api_host
+
+    # Delete cached discovery document.
+    shutil.rmtree(arvados.http_cache('discovery'))
+
+    pid_file = os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH)
+    pid_file_ok = find_server_pid(pid_file, 0)
+
+    existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
+    if existing_api_host and pid_file_ok:
+        if existing_api_host == my_api_host:
+            try:
+                return reset()
+            except:
+                # Fall through to shutdown-and-start case.
+                pass
+        else:
+            # Server was provided by parent. Can't recover if it's
+            # unresettable.
+            return reset()
+
+    # Before trying to start up our own server, call stop() to avoid
+    # "Phusion Passenger Standalone is already running on PID 12345".
+    # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
+    # we know the server is ours to kill.)
+    stop(force=True)
+
+    restore_cwd = os.getcwd()
+    api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
+    os.chdir(api_src_dir)
+
+    # Either we haven't started a server of our own yet, or it has
+    # died, or we have lost our credentials, or something else is
+    # preventing us from calling reset(). Start a new one.
+
+    if not os.path.exists('tmp/self-signed.pem'):
+        # We assume here that either passenger reports its listening
+        # address as https:/0.0.0.0:port/. If it reports "127.0.0.1"
+        # then the certificate won't match the host and reset() will
+        # fail certificate verification. If it reports "localhost",
+        # clients (notably Python SDK's websocket client) might
+        # resolve localhost as ::1 and then fail to connect.
+        subprocess.check_call([
+            'openssl', 'req', '-new', '-x509', '-nodes',
+            '-out', 'tmp/self-signed.pem',
+            '-keyout', 'tmp/self-signed.key',
+            '-days', '3650',
+            '-subj', '/CN=0.0.0.0'],
+        stdout=sys.stderr)
+
+    port = find_available_port()
+    env = os.environ.copy()
+    env['RAILS_ENV'] = 'test'
+    env['ARVADOS_WEBSOCKETS'] = 'yes'
+    env.pop('ARVADOS_TEST_API_HOST', None)
+    env.pop('ARVADOS_API_HOST', None)
+    env.pop('ARVADOS_API_HOST_INSECURE', None)
+    env.pop('ARVADOS_API_TOKEN', None)
+    start_msg = subprocess.check_output(
+        ['bundle', 'exec',
+         'passenger', 'start', '-d', '-p{}'.format(port),
+         '--pid-file', os.path.join(os.getcwd(), pid_file),
+         '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
+         '--ssl',
+         '--ssl-certificate', 'tmp/self-signed.pem',
+         '--ssl-certificate-key', 'tmp/self-signed.key'],
+        env=env)
+
+    if not leave_running_atexit:
+        atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
+
+    match = re.search(r'Accessible via: https://(.*?)/', start_msg)
+    if not match:
+        raise Exception(
+            "Passenger did not report endpoint: {}".format(start_msg))
+    my_api_host = match.group(1)
+    os.environ['ARVADOS_API_HOST'] = my_api_host
+
+    # Make sure the server has written its pid file before continuing
+    find_server_pid(pid_file)
+
+    reset()
+    os.chdir(restore_cwd)
+
+def reset():
+    """Reset the test server to fixture state.
+
+    This resets the ARVADOS_TEST_API_HOST provided by a parent process
+    if any, otherwise the server started by run().
+
+    It also resets ARVADOS_* environment vars to point to the test
+    server with admin credentials.
+    """
+    existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
+    token = auth_token('admin')
+    httpclient = httplib2.Http(ca_certs=os.path.join(
+        SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
+    httpclient.request(
+        'https://{}/database/reset'.format(existing_api_host),
+        'POST',
+        headers={'Authorization': 'OAuth2 {}'.format(token)})
+    os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
+    os.environ['ARVADOS_API_HOST'] = existing_api_host
+    os.environ['ARVADOS_API_TOKEN'] = token
+
+def stop(force=False):
+    """Stop the API server, if one is running.
+
+    If force==False, kill it only if we started it ourselves. (This
+    supports the use case where a Python test suite calls run(), but
+    run() just uses the ARVADOS_TEST_API_HOST provided by the parent
+    process, and the test suite cleans up after itself by calling
+    stop(). In this case the test server provided by the parent
+    process should be left alone.)
+
+    If force==True, kill it even if we didn't start it
+    ourselves. (This supports the use case in __main__, where "run"
+    and "stop" happen in different processes.)
+    """
+    global my_api_host
+    if force or my_api_host is not None:
+        kill_server_pid(os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH))
+        my_api_host = None
 
 def _start_keep(n, keep_args):
     keep0 = tempfile.mkdtemp()
+    port = find_available_port()
     keep_cmd = ["keepstore",
                 "-volumes={}".format(keep0),
-                "-listen=:{}".format(25107+n),
+                "-listen=:{}".format(port),
                 "-pid={}".format("{}/keep{}.pid".format(TEST_TMPDIR, n))]
 
     for arg, val in keep_args.iteritems():
@@ -151,12 +270,11 @@ def _start_keep(n, keep_args):
     with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'w') as f:
         f.write(keep0)
 
+    return port
+
 def run_keep(blob_signing_key=None, enforce_permissions=False):
     stop_keep()
 
-    if not os.path.exists(TEST_TMPDIR):
-        os.mkdir(TEST_TMPDIR)
-
     keep_args = {}
     if blob_signing_key:
         with open(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"), "w") as f:
@@ -165,33 +283,28 @@ def run_keep(blob_signing_key=None, enforce_permissions=False):
     if enforce_permissions:
         keep_args['--enforce-permissions'] = 'true'
 
-    _start_keep(0, keep_args)
-    _start_keep(1, keep_args)
-
-    os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3000"
-    os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
-
-    authorize_with("admin")
-    api = arvados.api('v1', cache=False)
+    api = arvados.api(
+        'v1', cache=False,
+        host=os.environ['ARVADOS_API_HOST'],
+        token=os.environ['ARVADOS_API_TOKEN'],
+        insecure=True)
     for d in api.keep_services().list().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()
 
-    s1 = api.keep_services().create(body={"keep_service": {
-                "uuid": "zzzzz-bi6l4-5bo5n1iekkjyz6b",
-                "service_host": "localhost",
-                "service_port": 25107,
-                "service_type": "disk"
-                }}).execute()
-    s2 = api.keep_services().create(body={"keep_service": {
-                "uuid": "zzzzz-bi6l4-2nz60e0ksj7vr3s",
-                "service_host": "localhost",
-                "service_port": 25108,
-                "service_type": "disk"
-                }}).execute()
-    api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s1["uuid"] } }).execute()
-    api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s2["uuid"] } }).execute()
+    for d in range(0, 2):
+        port = _start_keep(d, keep_args)
+        svc = api.keep_services().create(body={'keep_service': {
+            'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
+            'service_host': 'localhost',
+            'service_port': port,
+            'service_type': 'disk',
+            'service_ssl_flag': False,
+        }}).execute()
+        api.keep_disks().create(body={
+            'keep_disk': {'keep_service_uuid': svc['uuid'] }
+        }).execute()
 
 def _stop_keep(n):
     kill_server_pid("{}/keep{}.pid".format(TEST_TMPDIR, n), 0)
@@ -206,25 +319,34 @@ def stop_keep():
     _stop_keep(0)
     _stop_keep(1)
 
-def run_keep_proxy(auth):
+def run_keep_proxy():
     stop_keep_proxy()
 
-    if not os.path.exists(TEST_TMPDIR):
-        os.mkdir(TEST_TMPDIR)
-
-    os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3000"
-    os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
-    os.environ["ARVADOS_API_TOKEN"] = fixture("api_client_authorizations")[auth]["api_token"]
-
-    kp0 = subprocess.Popen(["keepproxy",
-                            "-pid={}/keepproxy.pid".format(TEST_TMPDIR),
-                            "-listen=:{}".format(25101)])
-
-    authorize_with("admin")
-    api = arvados.api('v1', cache=False)
-    api.keep_services().create(body={"keep_service": {"service_host": "localhost",  "service_port": 25101, "service_type": "proxy"} }).execute()
-
-    os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:25101"
+    admin_token = auth_token('admin')
+    port = find_available_port()
+    env = os.environ.copy()
+    env['ARVADOS_API_TOKEN'] = admin_token
+    kp = subprocess.Popen(
+        ['keepproxy',
+         '-pid={}/keepproxy.pid'.format(TEST_TMPDIR),
+         '-listen=:{}'.format(port)],
+        env=env)
+
+    api = arvados.api(
+        'v1', cache=False,
+        host=os.environ['ARVADOS_API_HOST'],
+        token=admin_token,
+        insecure=True)
+    for d in api.keep_services().list(
+            filters=[['service_type','=','proxy']]).execute()['items']:
+        api.keep_services().delete(uuid=d['uuid']).execute()
+    api.keep_services().create(body={'keep_service': {
+        'service_host': 'localhost',
+        'service_port': port,
+        'service_type': 'proxy',
+        'service_ssl_flag': False,
+    }}).execute()
+    os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:{}".format(port)
 
 def stop_keep_proxy():
     kill_server_pid(os.path.join(TEST_TMPDIR, "keepproxy.pid"), 0)
@@ -241,9 +363,12 @@ def fixture(fix):
           pass
         return yaml.load(yaml_file)
 
-def authorize_with(token):
-    '''token is the symbolic name of the token from the api_client_authorizations fixture'''
-    arvados.config.settings()["ARVADOS_API_TOKEN"] = fixture("api_client_authorizations")[token]["api_token"]
+def auth_token(token_name):
+    return fixture("api_client_authorizations")[token_name]["api_token"]
+
+def authorize_with(token_name):
+    '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
+    arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
     arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
     arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
 
@@ -279,16 +404,15 @@ class TestCaseWithServers(unittest.TestCase):
         cls._orig_environ = os.environ.copy()
         cls._orig_config = arvados.config.settings().copy()
         cls._cleanup_funcs = []
+        os.environ.pop('ARVADOS_KEEP_PROXY', None)
+        os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
         for server_kwargs, start_func, stop_func in (
-              (cls.MAIN_SERVER, run, stop),
-              (cls.KEEP_SERVER, run_keep, stop_keep),
-              (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
+                (cls.MAIN_SERVER, run, reset),
+                (cls.KEEP_SERVER, run_keep, stop_keep),
+                (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
             if server_kwargs is not None:
                 start_func(**server_kwargs)
                 cls._cleanup_funcs.append(stop_func)
-        os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
-        if cls.KEEP_PROXY_SERVER is None:
-            os.environ.pop('ARVADOS_KEEP_PROXY', None)
         if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
             cls.local_store = tempfile.mkdtemp()
             os.environ['KEEP_LOCAL_STORE'] = cls.local_store
@@ -307,29 +431,34 @@ class TestCaseWithServers(unittest.TestCase):
 
 
 if __name__ == "__main__":
+    actions = ['start', 'stop',
+               'start_keep', 'stop_keep',
+               'start_keep_proxy', 'stop_keep_proxy']
     parser = argparse.ArgumentParser()
-    parser.add_argument('action', type=str, help='''one of "start", "stop", "start_keep", "stop_keep"''')
-    parser.add_argument('--websockets', action='store_true', default=False)
-    parser.add_argument('--reuse', action='store_true', default=False)
-    parser.add_argument('--auth', type=str, help='Print authorization info for given api_client_authorizations fixture')
+    parser.add_argument('action', type=str, help="one of {}".format(actions))
+    parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
     args = parser.parse_args()
 
     if args.action == 'start':
-        run(websockets=args.websockets, reuse_server=args.reuse)
+        stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
+        run(leave_running_atexit=True)
+        host = os.environ['ARVADOS_API_HOST']
         if args.auth is not None:
-            authorize_with(args.auth)
-            print("export ARVADOS_API_HOST={}".format(arvados.config.settings()["ARVADOS_API_HOST"]))
-            print("export ARVADOS_API_TOKEN={}".format(arvados.config.settings()["ARVADOS_API_TOKEN"]))
-            print("export ARVADOS_API_HOST_INSECURE={}".format(arvados.config.settings()["ARVADOS_API_HOST_INSECURE"]))
+            token = auth_token(args.auth)
+            print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
+            print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
+            print("export ARVADOS_API_HOST_INSECURE=true")
+        else:
+            print(host)
     elif args.action == 'stop':
-        stop()
+        stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
     elif args.action == 'start_keep':
         run_keep()
     elif args.action == 'stop_keep':
         stop_keep()
     elif args.action == 'start_keep_proxy':
-        run_keep_proxy("admin")
+        run_keep_proxy()
     elif args.action == 'stop_keep_proxy':
         stop_keep_proxy()
     else:
-        print('Unrecognized action "{}", actions are "start", "stop", "start_keep", "stop_keep"'.format(args.action))
+        print("Unrecognized action '{}'. Actions are: {}.".format(args.action, actions))
index 4d03ae7744814f8629edd0b9cc37be7d9b41269c..576e47ae3d661f5469409651e597cc26ad0f5499 100644 (file)
@@ -48,9 +48,8 @@ class ArvadosApiClientTest(unittest.TestCase):
                               insecure=True,
                               requestBuilder=req_builder)
 
-    @classmethod
-    def tearDownClass(cls):
-        run_test_server.stop()
+    def tearDown(cls):
+        run_test_server.reset()
 
     def test_new_api_objects_with_cache(self):
         clients = [arvados.api('v1', cache=True,
index 3a2aaf2bc8eb59392d96e55a6b6f937910ccecdd..6d4d3cd8b816ea3854151285cd46cdebf60f5e63 100644 (file)
@@ -189,11 +189,12 @@ class KeepOptionalPermission(run_test_server.TestCaseWithServers):
 class KeepProxyTestCase(run_test_server.TestCaseWithServers):
     MAIN_SERVER = {}
     KEEP_SERVER = {}
-    KEEP_PROXY_SERVER = {'auth': 'admin'}
+    KEEP_PROXY_SERVER = {}
 
     @classmethod
     def setUpClass(cls):
         super(KeepProxyTestCase, cls).setUpClass()
+        run_test_server.authorize_with('active')
         cls.api_client = arvados.api('v1')
 
     def tearDown(self):
index bc82271f9c4f0019e6565086f2405e20ee8d3bcc..fa9fef28266bd668d61b1513adcf47395a937a6d 100644 (file)
@@ -7,9 +7,9 @@ import arvados
 import apiclient
 import run_test_server
 
-class PipelineTemplateTest(unittest.TestCase):
-    def setUp(self):
-        run_test_server.run()
+class PipelineTemplateTest(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+    KEEP_SERVER = {}
 
     def runTest(self):
         run_test_server.authorize_with("admin")
@@ -55,6 +55,3 @@ class PipelineTemplateTest(unittest.TestCase):
             geterror_response = arvados.api('v1').pipeline_templates().get(
                 uuid=pt_uuid
                 ).execute()
-
-    def tearDown(self):
-        run_test_server.stop()
index 45dd28a1575f416989b42ee18cc53e35d826f3b4..d879ebe1f8062c02d965bd9c845e5e00c57d1e76 100644 (file)
@@ -3,34 +3,38 @@ import run_test_server
 import unittest
 import arvados
 import arvados.events
+import mock
 import threading
 
-class EventTestBase(object):
-    def runTest(self):
-        run_test_server.authorize_with("admin")
+class WebsocketTest(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+
+    def setUp(self):
+        self.ws = None
+
+    def tearDown(self):
+        if self.ws:
+            self.ws.close()
+        super(WebsocketTest, self).tearDown()
+
+    def _test_subscribe(self, poll_fallback, expect_type):
+        run_test_server.authorize_with('active')
         events = Queue.Queue(3)
         self.ws = arvados.events.subscribe(
             arvados.api('v1'), [['object_uuid', 'is_a', 'arvados#human']],
-            events.put, poll_fallback=2)
-        self.assertIsInstance(self.ws, self.WS_TYPE)
+            events.put, poll_fallback=poll_fallback)
+        self.assertIsInstance(self.ws, expect_type)
         self.assertEqual(200, events.get(True, 10)['status'])
         human = arvados.api('v1').humans().create(body={}).execute()
         self.assertEqual(human['uuid'], events.get(True, 10)['object_uuid'])
         self.assertTrue(events.empty(), "got more events than expected")
 
-    def tearDown(self):
-        try:
-            self.ws.close()
-        except AttributeError:
-            pass
-        super(EventTestBase, self).tearDown()
-
+    def test_subscribe_websocket(self):
+        self._test_subscribe(
+            poll_fallback=False, expect_type=arvados.events.EventClient)
 
-class WebsocketTest(EventTestBase, run_test_server.TestCaseWithServers):
-    MAIN_SERVER = {'websockets': True}
-    WS_TYPE = arvados.events.EventClient
-
-
-class PollClientTest(EventTestBase, run_test_server.TestCaseWithServers):
-    MAIN_SERVER = {}
-    WS_TYPE = arvados.events.PollClient
+    @mock.patch('arvados.events.EventClient.__init__')
+    def test_subscribe_poll(self, event_client_constr):
+        event_client_constr.side_effect = Exception('All is well')
+        self._test_subscribe(
+            poll_fallback=1, expect_type=arvados.events.PollClient)
index c1d5219b07c9a6d243a3bb7e7a142f5064cdfd8e..4ad5e10faa46b96222d4291596d2f47f686bf18c 100644 (file)
@@ -3,6 +3,7 @@
 
 # Ignore all logfiles and tempfiles.
 /log/*.log
+/log/*.log.gz
 /tmp
 
 # Sensitive files and local configuration
index 20a0f00f14bb79dfa752e14a06f49a2966b25ec8..dce36f54719d3cff317b16c131a7d41385544263 100644 (file)
@@ -57,7 +57,7 @@ gem 'rvm-capistrano', :group => :test
 
 gem 'acts_as_api'
 
-gem 'passenger', :group => :production
+gem 'passenger'
 
 gem 'omniauth', '1.1.1'
 gem 'omniauth-oauth2', '1.1.1'
index ed2c533f5662e008bc4d25f5ae4b29c7556b0a49..2d62e40f034a6bcbaca3697768d9ca7e48246ad4 100644 (file)
@@ -45,7 +45,6 @@ test:
   blob_signing_key: zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc
   user_profile_notification_address: arvados@example.com
   workbench_address: https://localhost:3001/
-  websocket_address: ws://127.0.0.1:3333/websocket
 
 common:
   uuid_prefix: <%= Digest::MD5.hexdigest(`hostname`).to_i(16).to_s(36)[0..4] %>
index df72e246a6cedadf53386c3420c6f8d168ded8d6..65af8ce2bd9a732e1e7d2ace6772f618670cc5dc 100644 (file)
@@ -7,7 +7,7 @@ class WebsocketTestRunner < MiniTest::Unit
   def _system(*cmd)
     Bundler.with_clean_env do
       if not system({'ARVADOS_WEBSOCKETS' => 'ws-only', 'RAILS_ENV' => 'test'}, *cmd)
-        raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}"
+        raise RuntimeError, "Command failed with exit status #{$?}: #{cmd.inspect}"
       end
     end
   end
@@ -34,7 +34,13 @@ class WebsocketTestRunner < MiniTest::Unit
     begin
       super(args)
     ensure
-      Process.kill('TERM', server_pid)
+      Dir.chdir($ARV_API_SERVER_DIR) do
+        _system('passenger', 'stop', '-p3002')
+      end
+      # DatabaseCleaner leaves the database empty. Prefer to leave it full.
+      dc = DatabaseController.new
+      dc.define_singleton_method :render do |*args| end
+      dc.reset
     end
   end
 end
index b68574c53d55436b27396576699c29fbdcddcb63..73a609c3a99d0cc181165325366805a92f7aa764 100644 (file)
@@ -480,8 +480,8 @@ class TagsDirectory(RecursiveInvalidateDirectory):
                 ).execute(num_retries=self.num_retries)
         if "items" in tags:
             self.merge(tags['items'],
-                       lambda i: i['name'] if 'name' in i else i['uuid'],
-                       lambda a, i: a.tag == i,
+                       lambda i: i['name'],
+                       lambda a, i: a.tag == i['name'],
                        lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, i['name'], poll=self._poll, poll_time=self._poll_time))
 
 
index 7ab0f839df5ffb0e5f05e524e2daa0eec83ad560..104373b7ad3972af52e2a17d27573e22b759506e 100644 (file)
@@ -17,22 +17,20 @@ class MountTestBase(unittest.TestCase):
         self.keeptmp = tempfile.mkdtemp()
         os.environ['KEEP_LOCAL_STORE'] = self.keeptmp
         self.mounttmp = tempfile.mkdtemp()
-        run_test_server.run(False)
+        run_test_server.run()
         run_test_server.authorize_with("admin")
-        self.api = api = fuse.SafeApi(arvados.config)
+        self.api = fuse.SafeApi(arvados.config)
 
-    def make_mount(self, root_class, *root_args):
+    def make_mount(self, root_class, **root_kwargs):
         operations = fuse.Operations(os.getuid(), os.getgid())
         operations.inodes.add_entry(root_class(
-                llfuse.ROOT_INODE, operations.inodes, self.api, 0, *root_args))
+            llfuse.ROOT_INODE, operations.inodes, self.api, 0, **root_kwargs))
         llfuse.init(operations, self.mounttmp, [])
         threading.Thread(None, llfuse.main).start()
         # wait until the driver is finished initializing
         operations.initlock.wait()
 
     def tearDown(self):
-        run_test_server.stop()
-
         # llfuse.close is buggy, so use fusermount instead.
         #llfuse.close(unmount=True)
         count = 0
@@ -44,6 +42,7 @@ class MountTestBase(unittest.TestCase):
 
         os.rmdir(self.mounttmp)
         shutil.rmtree(self.keeptmp)
+        run_test_server.reset()
 
     def assertDirContents(self, subdir, expect_content):
         path = self.mounttmp
@@ -96,7 +95,7 @@ class FuseMountTest(MountTestBase):
         self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
 
     def runTest(self):
-        self.make_mount(fuse.CollectionDirectory, self.testcollection)
+        self.make_mount(fuse.CollectionDirectory, collection=self.testcollection)
 
         self.assertDirContents(None, ['thing1.txt', 'thing2.txt',
                                       'edgecases', 'dir1', 'dir2'])
@@ -204,15 +203,8 @@ class FuseTagsUpdateTest(MountTestBase):
         }}).execute()
 
     def runTest(self):
-        operations = fuse.Operations(os.getuid(), os.getgid())
-        e = operations.inodes.add_entry(fuse.TagsDirectory(llfuse.ROOT_INODE, operations.inodes, self.api, 0, poll_time=1))
-
-        llfuse.init(operations, self.mounttmp, [])
-        t = threading.Thread(None, lambda: llfuse.main())
-        t.start()
+        self.make_mount(fuse.TagsDirectory, poll_time=1)
 
-        # wait until the driver is finished initializing
-        operations.initlock.wait()
         self.assertIn('foo_tag', os.listdir(self.mounttmp))
 
         bar_uuid = run_test_server.fixture('collections')['bar_file']['uuid']
@@ -234,7 +226,7 @@ class FuseTagsUpdateTest(MountTestBase):
 class FuseSharedTest(MountTestBase):
     def runTest(self):
         self.make_mount(fuse.SharedDirectory,
-                        self.api.users().current().execute()['uuid'])
+                        exclude=self.api.users().current().execute()['uuid'])
 
         # shared_dirs is a list of the directories exposed
         # by fuse.SharedDirectory (i.e. any object visible
@@ -277,7 +269,7 @@ class FuseSharedTest(MountTestBase):
 class FuseHomeTest(MountTestBase):
     def runTest(self):
         self.make_mount(fuse.ProjectDirectory,
-                        self.api.users().current().execute())
+                        project_object=self.api.users().current().execute())
 
         d1 = os.listdir(self.mounttmp)
         self.assertIn('Unrestricted public data', d1)
index 8acf43abd4eba5094d89e4443a0d6a067139aa80..ccbd7d8790c5042566aff7bb4941f56a0c91e26a 100644 (file)
@@ -5,6 +5,7 @@ import (
        "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"
@@ -13,7 +14,6 @@ import (
        "net/http"
        "net/url"
        "os"
-       "os/exec"
        "strings"
        "testing"
        "time"
@@ -30,11 +30,6 @@ var _ = Suite(&ServerRequiredSuite{})
 // Tests that require the Keep server running
 type ServerRequiredSuite struct{}
 
-func pythonDir() string {
-       cwd, _ := os.Getwd()
-       return fmt.Sprintf("%s/../../sdk/python/tests", cwd)
-}
-
 // 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.
@@ -57,45 +52,17 @@ func closeListener() {
 }
 
 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
-       cwd, _ := os.Getwd()
-       defer os.Chdir(cwd)
-
-       os.Chdir(pythonDir())
-       {
-               cmd := exec.Command("python", "run_test_server.py", "start")
-               stderr, err := cmd.StderrPipe()
-               if err != nil {
-                       log.Fatalf("Setting up stderr pipe: %s", err)
-               }
-               go io.Copy(os.Stderr, stderr)
-               if err := cmd.Run(); err != nil {
-                       panic(fmt.Sprintf("'python run_test_server.py start' returned error %s", err))
-               }
-       }
-       {
-               cmd := exec.Command("python", "run_test_server.py", "start_keep")
-               stderr, err := cmd.StderrPipe()
-               if err != nil {
-                       log.Fatalf("Setting up stderr pipe: %s", err)
-               }
-               go io.Copy(os.Stderr, stderr)
-               if err := cmd.Run(); err != nil {
-                       panic(fmt.Sprintf("'python run_test_server.py start_keep' returned error %s", err))
-               }
-       }
+       arvadostest.StartAPI()
+       arvadostest.StartKeep()
+}
 
-       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
+func (s *ServerRequiredSuite) SetUpTest(c *C) {
+       arvadostest.ResetEnv()
 }
 
 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
-       cwd, _ := os.Getwd()
-       defer os.Chdir(cwd)
-
-       os.Chdir(pythonDir())
-       exec.Command("python", "run_test_server.py", "stop_keep").Run()
-       exec.Command("python", "run_test_server.py", "stop").Run()
+       arvadostest.StopKeep()
+       arvadostest.StopAPI()
 }
 
 func setupProxyService() {
@@ -136,27 +103,37 @@ func setupProxyService() {
        }
 }
 
-func runProxy(c *C, args []string, token string, port int) keepclient.KeepClient {
-       os.Args = append(args, fmt.Sprintf("-listen=:%v", port))
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
-
-       listener = nil
-       go main()
-       time.Sleep(100 * time.Millisecond)
-
-       os.Setenv("ARVADOS_KEEP_PROXY", fmt.Sprintf("http://localhost:%v", port))
-       os.Setenv("ARVADOS_API_TOKEN", token)
+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, err := keepclient.MakeKeepClient(&arv)
-       c.Assert(err, Equals, nil)
+       kc := keepclient.KeepClient{
+               Arvados: &arv,
+               Want_replicas: 2,
+               Using_proxy: true,
+               Client: &http.Client{},
+       }
+       kc.SetServiceRoots(map[string]string{
+               "proxy": fmt.Sprintf("http://localhost:%v", port),
+       })
        c.Check(kc.Using_proxy, Equals, true)
        c.Check(len(kc.ServiceRoots()), Equals, 1)
        for _, root := range kc.ServiceRoots() {
                c.Check(root, Equals, fmt.Sprintf("http://localhost:%v", port))
        }
-       os.Setenv("ARVADOS_KEEP_PROXY", "")
        log.Print("keepclient created")
+       if bogusClientToken {
+               arvadostest.ResetEnv()
+       }
+
+       {
+               os.Args = append(args, fmt.Sprintf("-listen=:%v", port))
+               listener = nil
+               go main()
+       }
+
        return kc
 }
 
@@ -164,7 +141,6 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
        log.Print("TestPutAndGet start")
 
        os.Args = []string{"keepproxy", "-listen=:29950"}
-       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
        listener = nil
        go main()
        time.Sleep(100 * time.Millisecond)
@@ -183,7 +159,6 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
                c.Check(root, Equals, "http://localhost:29950")
        }
        os.Setenv("ARVADOS_EXTERNAL_CLIENT", "")
-       log.Print("keepclient created")
 
        waitForListener()
        defer closeListener()
@@ -248,12 +223,10 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
 func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
        log.Print("TestPutAskGetForbidden start")
 
-       kc := runProxy(c, []string{"keepproxy"}, "123abc", 29951)
+       kc := runProxy(c, []string{"keepproxy"}, 29951, true)
        waitForListener()
        defer closeListener()
 
-       log.Print("keepclient created")
-
        hash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
 
        {
@@ -290,7 +263,7 @@ func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
 func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
        log.Print("TestGetDisabled start")
 
-       kc := runProxy(c, []string{"keepproxy", "-no-get"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29952)
+       kc := runProxy(c, []string{"keepproxy", "-no-get"}, 29952, false)
        waitForListener()
        defer closeListener()
 
@@ -330,7 +303,7 @@ func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
 func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
        log.Print("TestPutDisabled start")
 
-       kc := runProxy(c, []string{"keepproxy", "-no-put"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29953)
+       kc := runProxy(c, []string{"keepproxy", "-no-put"}, 29953, false)
        waitForListener()
        defer closeListener()
 
@@ -346,7 +319,7 @@ func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
-       runProxy(c, []string{"keepproxy"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29954)
+       runProxy(c, []string{"keepproxy"}, 29954, false)
        waitForListener()
        defer closeListener()
 
@@ -378,7 +351,7 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPostWithoutHash(c *C) {
-       runProxy(c, []string{"keepproxy"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29955)
+       runProxy(c, []string{"keepproxy"}, 29955, false)
        waitForListener()
        defer closeListener()