Merge branch 'master' into 6518-crunch2-dispatch-slurm
authorPeter Amstutz <peter.amstutz@curoverse.com>
Wed, 16 Mar 2016 00:02:30 +0000 (20:02 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Wed, 16 Mar 2016 00:02:30 +0000 (20:02 -0400)
208 files changed:
Makefile [new file with mode: 0644]
README [deleted file]
README.md [new file with mode: 0644]
apps/workbench/app/models/job.rb
apps/workbench/app/views/jobs/_show_log.html.erb
apps/workbench/config/application.default.yml
apps/workbench/test/integration/websockets_test.rb
build/create-plot-data-from-log.sh [new file with mode: 0755]
build/libcloud-pin [new file with mode: 0644]
build/package-build-dockerfiles/.gitignore [new file with mode: 0644]
build/package-build-dockerfiles/Makefile [new file with mode: 0644]
build/package-build-dockerfiles/README [new file with mode: 0644]
build/package-build-dockerfiles/build-all-build-containers.sh [new file with mode: 0755]
build/package-build-dockerfiles/centos6/Dockerfile [new file with mode: 0644]
build/package-build-dockerfiles/debian7/Dockerfile [new file with mode: 0644]
build/package-build-dockerfiles/debian8/Dockerfile [new file with mode: 0644]
build/package-build-dockerfiles/ubuntu1204/Dockerfile [new file with mode: 0644]
build/package-build-dockerfiles/ubuntu1404/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/centos6/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/centos6/localrepo.repo [new file with mode: 0644]
build/package-test-dockerfiles/debian7/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/debian8/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/ubuntu1204/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/ubuntu1404/Dockerfile [new file with mode: 0644]
build/package-testing/common-test-packages.sh [new file with mode: 0755]
build/package-testing/deb-common-test-packages.sh [new file with mode: 0755]
build/package-testing/test-package-arvados-api-server.sh [new file with mode: 0755]
build/package-testing/test-package-arvados-node-manager.sh [new file with mode: 0755]
build/package-testing/test-package-arvados-sso-server.sh [new file with mode: 0755]
build/package-testing/test-package-arvados-workbench.sh [new file with mode: 0755]
build/package-testing/test-package-python27-python-arvados-fuse.sh [new file with mode: 0755]
build/package-testing/test-package-python27-python-arvados-python-client.sh [new file with mode: 0755]
build/package-testing/test-packages-centos6.sh [new file with mode: 0755]
build/package-testing/test-packages-debian7.sh [new symlink]
build/package-testing/test-packages-debian8.sh [new symlink]
build/package-testing/test-packages-ubuntu1204.sh [new symlink]
build/package-testing/test-packages-ubuntu1404.sh [new symlink]
build/rails-package-scripts/README.md [new file with mode: 0644]
build/rails-package-scripts/arvados-api-server.sh [new file with mode: 0644]
build/rails-package-scripts/arvados-sso-server.sh [new file with mode: 0644]
build/rails-package-scripts/arvados-workbench.sh [new file with mode: 0644]
build/rails-package-scripts/postinst.sh [new file with mode: 0644]
build/rails-package-scripts/postrm.sh [new file with mode: 0644]
build/rails-package-scripts/prerm.sh [new file with mode: 0644]
build/rails-package-scripts/step2.sh [new file with mode: 0644]
build/run-build-docker-images.sh [new file with mode: 0755]
build/run-build-docker-jobs-image.sh [new file with mode: 0755]
build/run-build-packages-all-targets.sh [new file with mode: 0755]
build/run-build-packages-one-target.sh [new file with mode: 0755]
build/run-build-packages-sso.sh [new file with mode: 0755]
build/run-build-packages.sh [new file with mode: 0755]
build/run-library.sh [new file with mode: 0755]
build/run-tests.sh [new file with mode: 0755]
crunch_scripts/crunchrunner [new file with mode: 0755]
doc/_config.yml
doc/_includes/_run_command_foreach_example.liquid
doc/install/arvbox.html.textile.liquid [new file with mode: 0644]
doc/install/index.html.textile.liquid
doc/install/install-crunch-dispatch.html.textile.liquid
sdk/cli/bin/crunch-job
sdk/cli/test/binstub_clean_fail/mount
sdk/cli/test/test_crunch-job.rb
sdk/cwl/arvados_cwl/__init__.py
sdk/cwl/setup.py
sdk/cwl/test_with_arvbox.sh [new file with mode: 0755]
sdk/cwl/tests/__init__.py [new file with mode: 0644]
sdk/cwl/tests/test_job.py [new file with mode: 0644]
sdk/go/crunchrunner/crunchrunner.go
sdk/python/arvados/api.py
sdk/python/arvados/commands/arv_copy.py
sdk/python/setup.py
sdk/ruby/arvados.gemspec
services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
services/api/app/controllers/arvados/v1/repositories_controller.rb
services/api/app/models/api_client_authorization.rb
services/api/config/application.default.yml
services/api/lib/crunch_dispatch.rb
services/api/test/fixtures/collections.yml
services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
services/api/test/functional/arvados/v1/repositories_controller_test.rb
services/api/test/unit/crunch_dispatch_test.rb
services/arv-git-httpd/gitolite_test.go
services/crunch-run/crunchrun.go
services/crunch-run/crunchrun_test.go
services/crunch-run/logging_test.go
services/crunch-run/upload.go
services/datamanager/collection/collection.go
services/datamanager/datamanager.go
services/datamanager/datamanager_test.go
services/nodemanager/arvnodeman/baseactor.py [new file with mode: 0644]
services/nodemanager/arvnodeman/clientactor.py
services/nodemanager/arvnodeman/computenode/dispatch/__init__.py
services/nodemanager/arvnodeman/computenode/driver/__init__.py
services/nodemanager/arvnodeman/computenode/driver/ec2.py
services/nodemanager/arvnodeman/computenode/driver/gce.py
services/nodemanager/arvnodeman/config.py
services/nodemanager/arvnodeman/daemon.py
services/nodemanager/arvnodeman/launcher.py
services/nodemanager/arvnodeman/timedcallback.py
services/nodemanager/tests/test_daemon.py
services/nodemanager/tests/test_failure.py [new file with mode: 0644]
services/nodemanager/tests/testutil.py
tools/arvbox/bin/arvbox [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/Dockerfile.base [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/Dockerfile.demo [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/Dockerfile.dev [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/application_yml_override.py [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/common.sh [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/createusers.sh [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/crunch-setup.sh [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/fuse.conf [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/gitolite.rc [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/keep-setup.sh [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/logger [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/runit-docker/.gitignore [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/LICENSE [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/Makefile [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/README.md [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/changelog [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/compat [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/control [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/copyright [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/docs [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/rules [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/runit-docker/debian/source/format [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker.c [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/runsu.sh [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/api/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/api/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/api/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/api/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/doc/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/doc/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/doc/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/doc/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/docker/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/docker/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/docker/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/gitolite/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/gitolite/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/gitolite/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/keep-web/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/keep-web/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keep-web/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keep-web/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/keepproxy/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/keepproxy/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepproxy/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepproxy/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/keepstore0/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/keepstore0/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepstore0/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepstore0/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/keepstore1/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/keepstore1/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepstore1/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/keepstore1/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/postgres/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/postgres/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/postgres/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/postgres/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/ready/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/ready/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/sdk/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/sdk/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/sdk/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/sdk/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/slurmctld/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/slurmctld/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/slurmctld/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/slurmd/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/slurmd/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/slurmd/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/ssh/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/ssh/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/ssh/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/sso/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/sso/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/sso/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/sso/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/vm/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/vm/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/vm/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/vm/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/workbench/log/main/.gitstub [new file with mode: 0644]
tools/arvbox/lib/arvbox/docker/service/workbench/log/run [new symlink]
tools/arvbox/lib/arvbox/docker/service/workbench/run [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/service/workbench/run-service [new file with mode: 0755]
tools/arvbox/lib/arvbox/docker/waitforpostgres.sh [new file with mode: 0755]
tools/crunchstat-summary/crunchstat_summary/summarizer.py

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..45c9547
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+export WORKSPACE?=$(shell pwd)
+help:
+       @echo >&2
+       @echo >&2 "There is no default make target here.  Did you mean 'make test'?"
+       @echo >&2
+       @echo >&2 "More info:"
+       @echo >&2 "  Installing              --> http://doc.arvados.org/install"
+       @echo >&2 "  Developing/contributing --> https://dev.arvados.org"
+       @echo >&2 "  Project home            --> https://arvados.org"
+       @echo >&2
+       @false
+test:
+       build/run-tests.sh ${TEST_FLAGS}
+packages:
+       build/run-build-packages-all-targets.sh ${PACKAGES_FLAGS}
+test-packages:
+       build/run-build-packages-all-targets.sh --test-packages ${PACKAGES_FLAGS}
diff --git a/README b/README
deleted file mode 100644 (file)
index 7a2c5ef..0000000
--- a/README
+++ /dev/null
@@ -1,21 +0,0 @@
-Welcome to Arvados!
-
-The main Arvados web site is 
-  https://arvados.org
-
-The Arvados public wiki is located at 
-  https://dev.arvados.org/projects/arvados/wiki
-
-The Arvados public bug tracker is located at 
-  https://dev.arvados.org/projects/arvados/issues
-
-For support see 
-  http://doc.arvados.org/user/getting_started/community.html
-
-Installation documentation is located at 
-  http://doc.arvados.org/install
-
-If you wish to build the documentation yourself, follow the instructions in
-doc/README to build the documentation, then consult the "Install Guide".
-
-See COPYING for information about Arvados Free Software licenses.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..629c2f0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+[Arvados](https://arvados.org) is a free software distributed computing platform
+for bioinformatics, data science, and high throughput analysis of massive data
+sets.  Arvados supports a variety of cloud, cluster and HPC environments.
+
+Arvados consists of:
+
+* *Keep*: a petabyte-scale content-addressed distributed storage system for managing and
+  storing collections of files, accessible via HTTP and FUSE mount.
+
+* *Crunch*: a Docker-based cluster and HPC workflow engine designed providing
+  strong versioning, reproducibilty, and provenance of computations.
+
+* Related services and components including a web workbench for managing files
+  and compute jobs, REST APIs, SDKs, and other tools.
+
+## Quick start
+
+Curoverse maintains an Arvados public cloud demo at
+[https://cloud.curoverse.com](https://cloud.curoverse.com).  A Google account
+is required to log in.
+
+To try out Arvados on your local workstation, you can use Arvbox, which
+provides Arvados components pre-installed in a Docker container (requires
+Docker 1.9+).  After cloning the Arvados git repository:
+
+```
+$ cd arvados/tools/arvbox/bin
+$ ./arvbox start localdemo
+```
+
+In this mode you will only be able to connect to Arvbox from the same host.  To
+configure Arvbox to be accessible over a network and for other options see
+http://doc.arvados.org/install/arvbox.html for details.
+
+## Documentation
+
+Complete documentation, including a User Guide, Installation documentation and
+API documentation is available at http://doc.arvados.org/
+
+If you wish to build the Arvados documentation from a local git clone, see
+doc/README.textile for instructions.
+
+## Community
+
+The [#arvados](irc://irc.oftc.net:6667/#arvados IRC) (Internet Relay Chat)
+channel at the
+[Open and Free Technology Community (irc.oftc.net)](http://www.oftc.net/oftc/)
+is available for live discussion and support.  You can use a traditional IRC
+client or [join OFTC over the web.](https://webchat.oftc.net/?channels=arvados)
+
+The
+[Arvados user mailing list](http://lists.arvados.org/mailman/listinfo/arvados)
+is a forum for general discussion, questions, and news about Arvados
+development.  The
+[Arvados developer mailing list](http://lists.arvados.org/mailman/listinfo/arvados-dev)
+is a forum for more technical discussion, intended for developers and
+contributers to Arvados.
+
+## Development
+
+[![Build Status](https://ci.curoverse.com/buildStatus/icon?job=arvados-api-server)](https://ci.curoverse.com/job/arvados-api-server/)
+
+The Arvados public bug tracker is located at https://dev.arvados.org/projects/arvados/issues
+
+Continuous integration is hosted at https://ci.curoverse.com/
+
+Instructions for setting up a development environment and working on specific
+components can be found on the
+["Hacking Arvados" page of the Arvados wiki](https://dev.arvados.org/projects/arvados/wiki/Hacking).
+
+## Licensing
+
+Arvados is Free Software.  See COPYING for information about Arvados Free
+Software licenses.
index 3ece865959f10aabf70acfab6cde1dd9145cf6d5..6566aeb7cd5f82c4aa9e8f16a88b4d287f1afc24 100644 (file)
@@ -41,4 +41,16 @@ class Job < ArvadosBase
   def textile_attributes
     [ 'description' ]
   end
+
+  def stderr_log_query(limit=nil)
+    query = Log.where(event_type: "stderr", object_uuid: self.uuid)
+               .order("id DESC")
+    query = query.limit(limit) if limit
+    query
+  end
+
+  def stderr_log_lines(limit=2000)
+    stderr_log_query(limit).results.reverse.
+      flat_map { |log| log.properties[:text].split("\n") rescue [] }
+  end
 end
index 7d67b74210812895347d21d6c08f4ceb15c50db0..02ad2b73986856185e6b66bbaacd6d8a1a5826c7 100644 (file)
@@ -8,7 +8,8 @@
 <div id="event_log_div"
      class="arv-log-event-listener arv-log-event-handler-append-logs arv-job-log-window"
      data-object-uuid="<%= @object.uuid %>"
-     ></div>
+  ><%= @object.stderr_log_lines(Rails.configuration.running_job_log_records_to_fetch).join("\n") %>
+</div>
 
 <%# Applying a long throttle suppresses the auto-refresh of this
     partial that would normally be triggered by arv-log-event. %>
index 239ffcd225da24a0c444851c16e2484c1293ba96..5400debbfdaf55e1f64c004adf70f98ca4037cb1 100644 (file)
@@ -272,3 +272,7 @@ common:
   #
   # The default setting (false) is appropriate for a multi-user site.
   trust_all_content: false
+
+  # Maximum number of historic log records of a running job to fetch
+  # and display in the Log tab, while subscribing to web sockets.
+  running_job_log_records_to_fetch: 2000
index 648d59c69000b19cfca089d4aac154da5e2529c7..655ad92c94d1d18f23988990c59cb832a817fb9e 100644 (file)
@@ -211,4 +211,68 @@ class WebsocketTest < ActionDispatch::IntegrationTest
     datum = page.evaluate_script("jobGraphData[jobGraphData.length-1]['#{series}']")
     assert_in_epsilon value, datum.to_f
   end
+
+  test "test running job with just a few previous log records" do
+    Thread.current[:arvados_api_token] = @@API_AUTHS["admin"]['api_token']
+    job = Job.where(uuid: api_fixture("jobs")['running']['uuid']).results.first
+    visit page_with_token("admin", "/jobs/#{job.uuid}")
+
+    api = ArvadosApiClient.new
+
+    # Create just one old log record
+    api.api("logs", "", {log: {
+                object_uuid: job.uuid,
+                event_type: "stderr",
+                properties: {"text" => "Historic log message"}}})
+
+    click_link("Log")
+
+    # Expect "all" historic log records because we have less than
+    # default Rails.configuration.running_job_log_records_to_fetch count
+    assert_text 'Historic log message'
+
+    # Create new log record and expect it to show up in log tab
+    api.api("logs", "", {log: {
+                object_uuid: job.uuid,
+                event_type: "stderr",
+                properties: {"text" => "Log message after subscription"}}})
+    assert_text 'Log message after subscription'
+  end
+
+  test "test running job with too many previous log records" do
+    Rails.configuration.running_job_log_records_to_fetch = 5
+
+    Thread.current[:arvados_api_token] = @@API_AUTHS["admin"]['api_token']
+    job = Job.where(uuid: api_fixture("jobs")['running']['uuid']).results.first
+
+    visit page_with_token("admin", "/jobs/#{job.uuid}")
+
+    api = ArvadosApiClient.new
+
+    # Create Rails.configuration.running_job_log_records_to_fetch + 1 log records
+    (0..Rails.configuration.running_job_log_records_to_fetch).each do |count|
+      api.api("logs", "", {log: {
+                object_uuid: job.uuid,
+                event_type: "stderr",
+                properties: {"text" => "Old log message #{count}"}}})
+    end
+
+    # Go to log tab, which results in subscribing to websockets
+    click_link("Log")
+
+    # Expect all but the first historic log records,
+    # because that was one too many than fetch count.
+    (1..Rails.configuration.running_job_log_records_to_fetch).each do |count|
+      assert_text "Old log message #{count}"
+    end
+    assert_no_text 'Old log message 0'
+
+    # Create one more log record after subscription
+    api.api("logs", "", {log: {
+                object_uuid: job.uuid,
+                event_type: "stderr",
+                properties: {"text" => "Life goes on!"}}})
+    # Expect it to show up in log tab
+    assert_text 'Life goes on!'
+  end
 end
diff --git a/build/create-plot-data-from-log.sh b/build/create-plot-data-from-log.sh
new file mode 100755 (executable)
index 0000000..ce3bfed
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+build=$1
+file=$2
+outputdir=$3
+
+usage() {
+    echo "./$0 build_number file_to_parse output_dir"
+    echo "this script will use the build output to generate *csv and *txt"
+    echo "for jenkins plugin plot https://github.com/jenkinsci/plot-plugin/"
+}
+
+if [ $# -ne 3 ]
+then
+    usage
+    exit 1
+fi
+
+if [ ! -e $file ]
+then
+    usage
+    echo "$file doesn't exist! exiting"
+    exit 2
+fi
+if [ ! -w $outputdir ]
+then
+    usage
+    echo "$outputdir isn't writeable! exiting"
+    exit 3
+fi
+
+#------------------------------
+## MAXLINE is the amount of lines that will read after the pattern
+## is match (the logfile could be hundred thousands lines long).
+## 1000 should be safe enough to capture all the output of the individual test
+MAXLINES=1000
+
+## TODO: check $build and $file make sense
+
+for test in \
+ test_Create_and_show_large_collection_with_manifest_text_of_20000000 \
+ test_Create,_show,_and_update_description_for_large_collection_with_manifest_text_of_100000 \
+ test_Create_one_large_collection_of_20000000_and_one_small_collection_of_10000_and_combine_them
+do
+ cleaned_test=$(echo $test | tr -d ",.:;/")
+ (zgrep -i -E -A$MAXLINES "^[A-Za-z0-9]+Test: $test" $file && echo "----") | tail -n +1 | tail --lines=+3|grep -B$MAXLINES -E "^-*$" -m1 > $outputdir/$cleaned_test-$build.txt
+ result=$?
+ if [ $result -eq 0 ]
+ then
+   echo processing  $outputdir/$cleaned_test-$build.txt creating  $outputdir/$cleaned_test.csv
+   echo $(grep ^Completed $outputdir/$cleaned_test-$build.txt | perl -n -e '/^Completed (.*) in [0-9]+ms.*$/;print "".++$line."-$1,";' | perl -p -e 's/,$//g'|tr " " "_" ) >  $outputdir/$cleaned_test.csv
+   echo $(grep ^Completed $outputdir/$cleaned_test-$build.txt | perl -n -e '/^Completed.*in ([0-9]+)ms.*$/;print "$1,";' | perl -p -e 's/,$//g' ) >>  $outputdir/$cleaned_test.csv
+   #echo URL=https://ci.curoverse.com/view/job/arvados-api-server/ws/apps/workbench/log/$cleaned_test-$build.txt/*view*/ >>  $outputdir/$test.properties
+ else
+   echo "$test was't found on $file"
+   cleaned_test=$(echo $test | tr -d ",.:;/")
+   >  $outputdir/$cleaned_test.csv
+ fi
+done
diff --git a/build/libcloud-pin b/build/libcloud-pin
new file mode 100644 (file)
index 0000000..3fa07e6
--- /dev/null
@@ -0,0 +1 @@
+LIBCLOUD_PIN=0.20.2.dev1
\ No newline at end of file
diff --git a/build/package-build-dockerfiles/.gitignore b/build/package-build-dockerfiles/.gitignore
new file mode 100644 (file)
index 0000000..ceee9fa
--- /dev/null
@@ -0,0 +1,2 @@
+*/generated
+common-generated/
diff --git a/build/package-build-dockerfiles/Makefile b/build/package-build-dockerfiles/Makefile
new file mode 100644 (file)
index 0000000..9216f82
--- /dev/null
@@ -0,0 +1,29 @@
+all: centos6/generated debian7/generated debian8/generated ubuntu1204/generated ubuntu1404/generated
+
+centos6/generated: common-generated-all
+       test -d centos6/generated || mkdir centos6/generated
+       cp -rlt centos6/generated common-generated/*
+
+debian7/generated: common-generated-all
+       test -d debian7/generated || mkdir debian7/generated
+       cp -rlt debian7/generated common-generated/*
+
+debian8/generated: common-generated-all
+       test -d debian8/generated || mkdir debian8/generated
+       cp -rlt debian8/generated common-generated/*
+
+ubuntu1204/generated: common-generated-all
+       test -d ubuntu1204/generated || mkdir ubuntu1204/generated
+       cp -rlt ubuntu1204/generated common-generated/*
+
+ubuntu1404/generated: common-generated-all
+       test -d ubuntu1404/generated || mkdir ubuntu1404/generated
+       cp -rlt ubuntu1404/generated common-generated/*
+
+common-generated-all: common-generated/golang-amd64.tar.gz
+
+common-generated/golang-amd64.tar.gz: common-generated
+       wget -cqO common-generated/golang-amd64.tar.gz http://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz
+
+common-generated:
+       mkdir common-generated
diff --git a/build/package-build-dockerfiles/README b/build/package-build-dockerfiles/README
new file mode 100644 (file)
index 0000000..0dfab94
--- /dev/null
@@ -0,0 +1,13 @@
+==================
+DOCKER IMAGE BUILD
+==================
+
+1. `make`
+2. `cd DISTRO`
+3. `docker build -t arvados/build:DISTRO .`
+
+==============
+BUILD PACKAGES
+==============
+
+`docker run -v /path/to/your/arvados-dev/jenkins:/jenkins -v /path/to/your/arvados:/arvados arvados/build:DISTRO`
diff --git a/build/package-build-dockerfiles/build-all-build-containers.sh b/build/package-build-dockerfiles/build-all-build-containers.sh
new file mode 100755 (executable)
index 0000000..34ffcce
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+make
+
+for target in `find -maxdepth 1 -type d |grep -v generated`; do
+  if [[ "$target" == "." ]]; then
+    continue
+  fi
+  target=${target#./}
+  echo $target
+  cd $target
+  docker build -t arvados/build:$target .
+  cd ..
+done
+
+
diff --git a/build/package-build-dockerfiles/centos6/Dockerfile b/build/package-build-dockerfiles/centos6/Dockerfile
new file mode 100644 (file)
index 0000000..cfd94c8
--- /dev/null
@@ -0,0 +1,31 @@
+FROM centos:6
+MAINTAINER Brett Smith <brett@curoverse.com>
+
+# Install build dependencies provided in base distribution
+RUN yum -q -y install make automake gcc gcc-c++ libyaml-devel patch readline-devel zlib-devel libffi-devel openssl-devel bzip2 libtool bison sqlite-devel rpm-build git perl-ExtUtils-MakeMaker libattr-devel nss-devel libcurl-devel which tar scl-utils centos-release-SCL postgresql-devel
+
+# Install golang binary
+ADD generated/golang-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+# Install RVM
+RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundler fpm
+
+# Need to "touch" RPM database to workaround bug in interaction between
+# overlayfs and yum (https://bugzilla.redhat.com/show_bug.cgi?id=1213602)
+RUN touch /var/lib/rpm/* && yum -q -y install python27 python33
+RUN scl enable python33 "easy_install-3.3 pip" && scl enable python27 "easy_install-2.7 pip"
+
+RUN cd /tmp && \
+    curl -OL 'http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm' && \
+    rpm -ivh rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm && \
+    sed -i 's/enabled = 0/enabled = 1/' /etc/yum.repos.d/rpmforge.repo
+
+RUN touch /var/lib/rpm/* && yum install --assumeyes git
+
+ENV WORKSPACE /arvados
+CMD ["scl", "enable", "python33", "python27", "/usr/local/rvm/bin/rvm-exec default bash /jenkins/run-build-packages.sh --target centos6"]
diff --git a/build/package-build-dockerfiles/debian7/Dockerfile b/build/package-build-dockerfiles/debian7/Dockerfile
new file mode 100644 (file)
index 0000000..0d04590
--- /dev/null
@@ -0,0 +1,19 @@
+FROM debian:wheezy
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+# Install dependencies and set up system.
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python2.7-dev python3 python-setuptools python3-setuptools libcurl4-gnutls-dev curl git procps libattr1-dev libfuse-dev libpq-dev python-pip
+
+# Install RVM
+RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundler fpm
+
+# Install golang binary
+ADD generated/golang-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+ENV WORKSPACE /arvados
+CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "debian7"]
diff --git a/build/package-build-dockerfiles/debian8/Dockerfile b/build/package-build-dockerfiles/debian8/Dockerfile
new file mode 100644 (file)
index 0000000..fcd390f
--- /dev/null
@@ -0,0 +1,19 @@
+FROM debian:jessie
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+# Install dependencies and set up system.
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python2.7-dev python3 python-setuptools python3-setuptools libcurl4-gnutls-dev curl git procps libattr1-dev libfuse-dev libgnutls28-dev libpq-dev python-pip
+
+# Install RVM
+RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundler fpm
+
+# Install golang binary
+ADD generated/golang-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+ENV WORKSPACE /arvados
+CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "debian8"]
diff --git a/build/package-build-dockerfiles/ubuntu1204/Dockerfile b/build/package-build-dockerfiles/ubuntu1204/Dockerfile
new file mode 100644 (file)
index 0000000..158053c
--- /dev/null
@@ -0,0 +1,19 @@
+FROM ubuntu:precise
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+# Install dependencies and set up system.
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python2.7-dev python3 python-setuptools python3-setuptools libcurl4-gnutls-dev curl git libattr1-dev libfuse-dev libpq-dev python-pip build-essential
+
+# Install RVM
+RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundler fpm
+
+# Install golang binary
+ADD generated/golang-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+ENV WORKSPACE /arvados
+CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu1204"]
diff --git a/build/package-build-dockerfiles/ubuntu1404/Dockerfile b/build/package-build-dockerfiles/ubuntu1404/Dockerfile
new file mode 100644 (file)
index 0000000..0b8ee7a
--- /dev/null
@@ -0,0 +1,19 @@
+FROM ubuntu:trusty
+MAINTAINER Brett Smith <brett@curoverse.com>
+
+# Install dependencies and set up system.
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python2.7-dev python3 python-setuptools python3-setuptools libcurl4-gnutls-dev curl git libattr1-dev libfuse-dev libpq-dev python-pip
+
+# Install RVM
+RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundler fpm
+
+# Install golang binary
+ADD generated/golang-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+ENV WORKSPACE /arvados
+CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu1404"]
diff --git a/build/package-test-dockerfiles/centos6/Dockerfile b/build/package-test-dockerfiles/centos6/Dockerfile
new file mode 100644 (file)
index 0000000..69927a1
--- /dev/null
@@ -0,0 +1,20 @@
+FROM centos:6
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+RUN yum -q install --assumeyes scl-utils centos-release-SCL \
+    which tar
+
+# Install RVM
+RUN touch /var/lib/rpm/* && \
+    gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1 && \
+    /usr/local/rvm/bin/rvm-exec default gem install bundle fpm
+
+RUN cd /tmp && \
+    curl -OL 'http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm' && \
+    rpm -ivh rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm && \
+    sed -i 's/enabled = 0/enabled = 1/' /etc/yum.repos.d/rpmforge.repo
+
+COPY localrepo.repo /etc/yum.repos.d/localrepo.repo
\ No newline at end of file
diff --git a/build/package-test-dockerfiles/centos6/localrepo.repo b/build/package-test-dockerfiles/centos6/localrepo.repo
new file mode 100644 (file)
index 0000000..ac6b898
--- /dev/null
@@ -0,0 +1,5 @@
+[localrepo]
+name=Arvados Test
+baseurl=file:///arvados/packages/centos6
+gpgcheck=0
+enabled=1
diff --git a/build/package-test-dockerfiles/debian7/Dockerfile b/build/package-test-dockerfiles/debian7/Dockerfile
new file mode 100644 (file)
index 0000000..c9a2fdc
--- /dev/null
@@ -0,0 +1,14 @@
+FROM debian:7
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+# Install RVM
+RUN apt-get update && apt-get -y install curl procps && \
+    gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1
+
+# udev daemon can't start in a container, so don't try.
+RUN mkdir -p /etc/udev/disabled
+
+RUN echo "deb file:///arvados/packages/debian7/ /" >>/etc/apt/sources.list
diff --git a/build/package-test-dockerfiles/debian8/Dockerfile b/build/package-test-dockerfiles/debian8/Dockerfile
new file mode 100644 (file)
index 0000000..cde1847
--- /dev/null
@@ -0,0 +1,14 @@
+FROM debian:8
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+# Install RVM
+RUN apt-get update && apt-get -y install curl && \
+    gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1
+
+# udev daemon can't start in a container, so don't try.
+RUN mkdir -p /etc/udev/disabled
+
+RUN echo "deb file:///arvados/packages/debian8/ /" >>/etc/apt/sources.list
diff --git a/build/package-test-dockerfiles/ubuntu1204/Dockerfile b/build/package-test-dockerfiles/ubuntu1204/Dockerfile
new file mode 100644 (file)
index 0000000..0cb77c8
--- /dev/null
@@ -0,0 +1,14 @@
+FROM ubuntu:precise
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+# Install RVM
+RUN apt-get update && apt-get -y install curl && \
+    gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1
+
+# udev daemon can't start in a container, so don't try.
+RUN mkdir -p /etc/udev/disabled
+
+RUN echo "deb file:///arvados/packages/ubuntu1204/ /" >>/etc/apt/sources.list
\ No newline at end of file
diff --git a/build/package-test-dockerfiles/ubuntu1404/Dockerfile b/build/package-test-dockerfiles/ubuntu1404/Dockerfile
new file mode 100644 (file)
index 0000000..6c4d0e9
--- /dev/null
@@ -0,0 +1,14 @@
+FROM ubuntu:trusty
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+# Install RVM
+RUN apt-get update && apt-get -y install curl && \
+    gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
+    curl -L https://get.rvm.io | bash -s stable && \
+    /usr/local/rvm/bin/rvm install 2.1 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.1
+
+# udev daemon can't start in a container, so don't try.
+RUN mkdir -p /etc/udev/disabled
+
+RUN echo "deb file:///arvados/packages/ubuntu1404/ /" >>/etc/apt/sources.list
\ No newline at end of file
diff --git a/build/package-testing/common-test-packages.sh b/build/package-testing/common-test-packages.sh
new file mode 100755 (executable)
index 0000000..2dc67ab
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -eu
+
+FAIL=0
+
+echo
+
+while read so && [ -n "$so" ]; do
+    if ldd "$so" | grep "not found" ; then
+        echo "^^^ Missing while scanning $so ^^^"
+        FAIL=1
+    fi
+done <<EOF
+$(find -name '*.so')
+EOF
+
+if test -x "/jenkins/package-testing/test-package-$1.sh" ; then
+    if ! "/jenkins/package-testing/test-package-$1.sh" ; then
+       FAIL=1
+    fi
+fi
+
+if test $FAIL = 0 ; then
+   echo "Package $1 passed"
+fi
+
+exit $FAIL
diff --git a/build/package-testing/deb-common-test-packages.sh b/build/package-testing/deb-common-test-packages.sh
new file mode 100755 (executable)
index 0000000..5f32a60
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -eu
+
+# Multiple .deb based distros symlink to this script, so extract the target
+# from the invocation path.
+target=$(echo $0 | sed 's/.*test-packages-\([^.]*\)\.sh.*/\1/')
+
+export ARV_PACKAGES_DIR="/arvados/packages/$target"
+
+dpkg-query --show > "$ARV_PACKAGES_DIR/$1.before"
+
+apt-get -qq update
+apt-get --assume-yes --force-yes install "$1"
+
+dpkg-query --show > "$ARV_PACKAGES_DIR/$1.after"
+
+set +e
+diff "$ARV_PACKAGES_DIR/$1.before" "$ARV_PACKAGES_DIR/$1.after" > "$ARV_PACKAGES_DIR/$1.diff"
+set -e
+
+mkdir -p /tmp/opts
+cd /tmp/opts
+
+export ARV_PACKAGES_DIR="/arvados/packages/$target"
+
+dpkg-deb -x $(ls -t "$ARV_PACKAGES_DIR/$1"_*.deb | head -n1) .
+
+while read so && [ -n "$so" ]; do
+    echo
+    echo "== Packages dependencies for $so =="
+    ldd "$so" | awk '($3 ~ /^\//){print $3}' | sort -u | xargs dpkg -S | cut -d: -f1 | sort -u
+done <<EOF
+$(find -name '*.so')
+EOF
+
+exec /jenkins/package-testing/common-test-packages.sh "$1"
diff --git a/build/package-testing/test-package-arvados-api-server.sh b/build/package-testing/test-package-arvados-api-server.sh
new file mode 100755 (executable)
index 0000000..e975448
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+cd /var/www/arvados-api/current/
+
+case "$TARGET" in
+    debian*|ubuntu*)
+        apt-get install -y nginx
+        dpkg-reconfigure arvados-api-server
+        ;;
+    centos6)
+        yum install --assumeyes httpd
+        yum reinstall --assumeyes arvados-api-server
+        ;;
+    *)
+        echo -e "$0: Unknown target '$TARGET'.\n" >&2
+        exit 1
+        ;;
+esac
+
+/usr/local/rvm/bin/rvm-exec default bundle list >"$ARV_PACKAGES_DIR/arvados-api-server.gems"
diff --git a/build/package-testing/test-package-arvados-node-manager.sh b/build/package-testing/test-package-arvados-node-manager.sh
new file mode 100755 (executable)
index 0000000..2f416d1
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+exec python <<EOF
+import libcloud.compute.types
+import libcloud.compute.providers
+libcloud.compute.providers.get_driver(libcloud.compute.types.Provider.AZURE_ARM)
+print "Successfully imported compatible libcloud library"
+EOF
diff --git a/build/package-testing/test-package-arvados-sso-server.sh b/build/package-testing/test-package-arvados-sso-server.sh
new file mode 100755 (executable)
index 0000000..c1a377e
--- /dev/null
@@ -0,0 +1,172 @@
+#!/bin/bash
+
+set -e
+
+EXITCODE=0
+DEBUG=${ARVADOS_DEBUG:-0}
+
+STDOUT_IF_DEBUG=/dev/null
+STDERR_IF_DEBUG=/dev/null
+DASHQ_UNLESS_DEBUG=-q
+if [[ "$DEBUG" != 0 ]]; then
+    STDOUT_IF_DEBUG=/dev/stdout
+    STDERR_IF_DEBUG=/dev/stderr
+    DASHQ_UNLESS_DEBUG=
+fi
+
+case "$TARGET" in
+    debian*|ubuntu*)
+        FORMAT=deb
+        ;;
+    centos6)
+        FORMAT=rpm
+        ;;
+    *)
+        echo -e "$0: Unknown target '$TARGET'.\n" >&2
+        exit 1
+        ;;
+esac
+
+if ! [[ -n "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: WORKSPACE environment variable not set"
+  echo >&2
+  exit 1
+fi
+
+if ! [[ -d "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: $WORKSPACE is not a directory"
+  echo >&2
+  exit 1
+fi
+
+title () {
+    txt="********** $1 **********"
+    printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
+}
+
+checkexit() {
+    if [[ "$1" != "0" ]]; then
+        title "!!!!!! $2 FAILED !!!!!!"
+    fi
+}
+
+
+# Find the SSO server package
+
+cd "$WORKSPACE"
+
+if [[ ! -d "/var/www/arvados-sso" ]]; then
+  echo "/var/www/arvados-sso should exist"
+  exit 1
+fi
+
+if [[ ! -e "/etc/arvados/sso/application.yml" ]]; then
+    mkdir -p /etc/arvados/sso/
+    RANDOM_PASSWORD=`date | md5sum |cut -f1 -d' '`
+    cp config/application.yml.example /etc/arvados/sso/application.yml
+    sed -i -e 's/uuid_prefix: ~/uuid_prefix: zzzzz/' /etc/arvados/sso/application.yml
+    sed -i -e "s/secret_token: ~/secret_token: $RANDOM_PASSWORD/" /etc/arvados/sso/application.yml
+fi
+
+if [[ ! -e "/etc/arvados/sso/database.yml" ]]; then
+  # We need to set up our database configuration now.
+  if [[ "$FORMAT" == "rpm" ]]; then
+    # postgres packaging on CentOS6 is kind of primitive, needs an initdb
+    service postgresql initdb
+    if [ "$TARGET" = "centos6" ]; then
+      sed -i -e "s/127.0.0.1\/32          ident/127.0.0.1\/32          md5/" /var/lib/pgsql/data/pg_hba.conf
+      sed -i -e "s/::1\/128               ident/::1\/128               md5/" /var/lib/pgsql/data/pg_hba.conf
+    fi
+  fi
+  service postgresql start
+
+  RANDOM_PASSWORD=`date | md5sum |cut -f1 -d' '`
+  cat >/etc/arvados/sso/database.yml <<EOF
+production:
+  adapter: postgresql
+  encoding: utf8
+  database: sso_provider_production
+  username: sso_provider_user
+  password: $RANDOM_PASSWORD
+  host: localhost
+EOF
+
+  su postgres -c "psql -c \"CREATE USER sso_provider_user WITH PASSWORD '$RANDOM_PASSWORD'\""
+  su postgres -c "createdb sso_provider_production -O sso_provider_user"
+fi
+
+if [[ "$FORMAT" == "deb" ]]; then
+  # Test 2: the package should reconfigure cleanly
+  dpkg-reconfigure arvados-sso-server || EXITCODE=3
+
+  cd /var/www/arvados-sso/current/
+  /usr/local/rvm/bin/rvm-exec default bundle list >"$ARV_PACKAGES_DIR/arvados-sso-server.gems"
+
+  # Test 3: the package should remove cleanly
+  apt-get remove arvados-sso-server --yes || EXITCODE=3
+
+  checkexit $EXITCODE "apt-get remove arvados-sso-server --yes"
+
+  # Test 4: the package configuration should remove cleanly
+  dpkg --purge arvados-sso-server || EXITCODE=4
+
+  checkexit $EXITCODE "dpkg --purge arvados-sso-server"
+
+  if [[ -e "/var/www/arvados-sso" ]]; then
+    EXITCODE=4
+  fi
+
+  checkexit $EXITCODE "leftover items under /var/www/arvados-sso"
+
+  # Test 5: the package should remove cleanly with --purge
+
+  apt-get remove arvados-sso-server --purge --yes || EXITCODE=5
+
+  checkexit $EXITCODE "apt-get remove arvados-sso-server --purge --yes"
+
+  if [[ -e "/var/www/arvados-sso" ]]; then
+    EXITCODE=5
+  fi
+
+  checkexit $EXITCODE "leftover items under /var/www/arvados-sso"
+
+elif [[ "$FORMAT" == "rpm" ]]; then
+
+  # Set up Nginx first
+  # (courtesy of https://www.phusionpassenger.com/library/walkthroughs/deploy/ruby/ownserver/nginx/oss/el6/install_passenger.html)
+  yum install -q -y epel-release pygpgme curl
+  curl --fail -sSLo /etc/yum.repos.d/passenger.repo https://oss-binaries.phusionpassenger.com/yum/definitions/el-passenger.repo
+  yum install -q -y nginx passenger
+  sed -i -e 's/^# passenger/passenger/' /etc/nginx/conf.d/passenger.conf
+  # Done setting up Nginx
+
+  # Test 2: the package should reinstall cleanly
+  yum --assumeyes reinstall arvados-sso-server || EXITCODE=3
+
+  cd /var/www/arvados-sso/current/
+  /usr/local/rvm/bin/rvm-exec default bundle list >$ARV_PACKAGES_DIR/arvados-sso-server.gems
+
+  # Test 3: the package should remove cleanly
+  yum -q -y remove arvados-sso-server || EXITCODE=3
+
+  checkexit $EXITCODE "yum -q -y remove arvados-sso-server"
+
+  if [[ -e "/var/www/arvados-sso" ]]; then
+    EXITCODE=3
+  fi
+
+  checkexit $EXITCODE "leftover items under /var/www/arvados-sso"
+
+fi
+
+if [[ "$EXITCODE" == "0" ]]; then
+  echo "Testing complete, no errors!"
+else
+  echo "Errors while testing!"
+fi
+
+exit $EXITCODE
diff --git a/build/package-testing/test-package-arvados-workbench.sh b/build/package-testing/test-package-arvados-workbench.sh
new file mode 100755 (executable)
index 0000000..1be4dea
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+cd /var/www/arvados-workbench/current/
+
+case "$TARGET" in
+    debian*|ubuntu*)
+        apt-get install -y nginx
+        dpkg-reconfigure arvados-workbench
+        ;;
+    centos6)
+        yum install --assumeyes httpd
+        yum reinstall --assumeyes arvados-workbench
+        ;;
+    *)
+        echo -e "$0: Unknown target '$TARGET'.\n" >&2
+        exit 1
+        ;;
+esac
+
+/usr/local/rvm/bin/rvm-exec default bundle list >"$ARV_PACKAGES_DIR/arvados-workbench.gems"
diff --git a/build/package-testing/test-package-python27-python-arvados-fuse.sh b/build/package-testing/test-package-python27-python-arvados-fuse.sh
new file mode 100755 (executable)
index 0000000..1654be9
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+exec python <<EOF
+import arvados_fuse
+print "Successfully imported arvados_fuse"
+EOF
diff --git a/build/package-testing/test-package-python27-python-arvados-python-client.sh b/build/package-testing/test-package-python27-python-arvados-python-client.sh
new file mode 100755 (executable)
index 0000000..0772fbf
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+exec python <<EOF
+import arvados
+print "Successfully imported arvados"
+EOF
diff --git a/build/package-testing/test-packages-centos6.sh b/build/package-testing/test-packages-centos6.sh
new file mode 100755 (executable)
index 0000000..4e05364
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+set -eu
+
+yum -q clean all
+touch /var/lib/rpm/*
+
+export ARV_PACKAGES_DIR=/arvados/packages/centos6
+
+rpm -qa | sort > "$ARV_PACKAGES_DIR/$1.before"
+
+yum install --assumeyes $1
+
+rpm -qa | sort > "$ARV_PACKAGES_DIR/$1.after"
+
+set +e
+diff "$ARV_PACKAGES_DIR/$1.before" "$ARV_PACKAGES_DIR/$1.after" >"$ARV_PACKAGES_DIR/$1.diff"
+set -e
+
+SCL=""
+if scl enable python27 true 2>/dev/null ; then
+    SCL="scl enable python27"
+fi
+
+mkdir -p /tmp/opts
+cd /tmp/opts
+
+rpm2cpio $(ls -t "$ARV_PACKAGES_DIR/$1"-*.rpm | head -n1) | cpio -idm 2>/dev/null
+
+shared=$(find -name '*.so')
+if test -n "$shared" ; then
+    for so in $shared ; do
+        echo
+        echo "== Packages dependencies for $so =="
+        $SCL ldd "$so" \
+            | awk '($3 ~ /^\//){print $3}' | sort -u | xargs rpm -qf | sort -u
+    done
+fi
+
+if test -n "$SCL" ; then
+    exec $SCL "/jenkins/package-testing/common-test-packages.sh '$1'"
+else
+    exec /jenkins/package-testing/common-test-packages.sh "$1"
+fi
diff --git a/build/package-testing/test-packages-debian7.sh b/build/package-testing/test-packages-debian7.sh
new file mode 120000 (symlink)
index 0000000..54ce94c
--- /dev/null
@@ -0,0 +1 @@
+deb-common-test-packages.sh
\ No newline at end of file
diff --git a/build/package-testing/test-packages-debian8.sh b/build/package-testing/test-packages-debian8.sh
new file mode 120000 (symlink)
index 0000000..54ce94c
--- /dev/null
@@ -0,0 +1 @@
+deb-common-test-packages.sh
\ No newline at end of file
diff --git a/build/package-testing/test-packages-ubuntu1204.sh b/build/package-testing/test-packages-ubuntu1204.sh
new file mode 120000 (symlink)
index 0000000..54ce94c
--- /dev/null
@@ -0,0 +1 @@
+deb-common-test-packages.sh
\ No newline at end of file
diff --git a/build/package-testing/test-packages-ubuntu1404.sh b/build/package-testing/test-packages-ubuntu1404.sh
new file mode 120000 (symlink)
index 0000000..54ce94c
--- /dev/null
@@ -0,0 +1 @@
+deb-common-test-packages.sh
\ No newline at end of file
diff --git a/build/rails-package-scripts/README.md b/build/rails-package-scripts/README.md
new file mode 100644 (file)
index 0000000..3a93c31
--- /dev/null
@@ -0,0 +1,14 @@
+When run-build-packages.sh builds a Rails package, it generates the package's pre/post-inst/rm scripts by concatenating:
+
+1. package_name.sh, which defines variables about where package files live and some human-readable names about them.
+2. step2.sh, which uses those to define some utility variables and set defaults for things that aren't set.
+3. stepname.sh, like postinst.sh, prerm.sh, etc., which uses all this information to do the actual work.
+
+Since our build process is a tower of shell scripts, concatenating files seemed like the least worst option to share code between these files and packages.  More advanced code generation would've been too much trouble to integrate into our build process at this time.  Trying to inject portions of files into other files seemed error-prone and likely to introduce bugs to the end result.
+
+postinst.sh lets the early parts define a few hooks to control behavior:
+
+* After it installs the core configuration files (database.yml, application.yml, and production.rb) to /etc/arvados/server, it calls setup_extra_conffiles.  By default this is a noop function (in step2.sh).  API server defines this to set up the old omniauth.rb conffile.
+* Before it restarts nginx, it calls setup_before_nginx_restart.  By default this is a noop function (in step2.sh).  API server defines this to set up the internal git repository, if necessary.
+* $RAILSPKG_DATABASE_LOAD_TASK defines the Rake task to load the database.  API server uses db:structure:load.  SSO server uses db:schema:load.  Workbench doesn't set this, which causes the postinst to skip all database work.
+* If $RAILSPKG_SUPPORTS_CONFIG_CHECK != 1, it won't run the config:check rake task.  SSO clears this flag (it doesn't have that task code).
diff --git a/build/rails-package-scripts/arvados-api-server.sh b/build/rails-package-scripts/arvados-api-server.sh
new file mode 100644 (file)
index 0000000..c2b99f0
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+# This file declares variables common to all scripts for one Rails package.
+
+PACKAGE_NAME=arvados-api-server
+INSTALL_PATH=/var/www/arvados-api
+CONFIG_PATH=/etc/arvados/api
+DOC_URL="http://doc.arvados.org/install/install-api-server.html#configure"
+
+RAILSPKG_DATABASE_LOAD_TASK=db:structure:load
+setup_extra_conffiles() {
+    setup_conffile initializers/omniauth.rb
+}
+
+setup_before_nginx_restart() {
+  # initialize git_internal_dir
+  # usually /var/lib/arvados/internal.git (set in application.default.yml )
+  if [ "$APPLICATION_READY" = "1" ]; then
+      GIT_INTERNAL_DIR=$($COMMAND_PREFIX bundle exec rake config:check 2>&1 | grep git_internal_dir | awk '{ print $2 }')
+      if [ ! -e "$GIT_INTERNAL_DIR" ]; then
+        run_and_report "Creating git_internal_dir '$GIT_INTERNAL_DIR'" \
+          mkdir -p "$GIT_INTERNAL_DIR"
+        run_and_report "Initializing git_internal_dir '$GIT_INTERNAL_DIR'" \
+          git init --quiet --bare $GIT_INTERNAL_DIR
+      else
+        echo "Initializing git_internal_dir $GIT_INTERNAL_DIR: directory exists, skipped."
+      fi
+      run_and_report "Making sure '$GIT_INTERNAL_DIR' has the right permission" \
+         chown -R "$WWW_OWNER:" "$GIT_INTERNAL_DIR"
+  else
+      echo "Initializing git_internal_dir... skipped."
+  fi
+}
diff --git a/build/rails-package-scripts/arvados-sso-server.sh b/build/rails-package-scripts/arvados-sso-server.sh
new file mode 100644 (file)
index 0000000..10b2ee2
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+# This file declares variables common to all scripts for one Rails package.
+
+PACKAGE_NAME=arvados-sso-server
+INSTALL_PATH=/var/www/arvados-sso
+CONFIG_PATH=/etc/arvados/sso
+DOC_URL="http://doc.arvados.org/install/install-sso.html#configure"
+RAILSPKG_DATABASE_LOAD_TASK=db:schema:load
+RAILSPKG_SUPPORTS_CONFIG_CHECK=0
diff --git a/build/rails-package-scripts/arvados-workbench.sh b/build/rails-package-scripts/arvados-workbench.sh
new file mode 100644 (file)
index 0000000..f2b8a56
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# This file declares variables common to all scripts for one Rails package.
+
+PACKAGE_NAME=arvados-workbench
+INSTALL_PATH=/var/www/arvados-workbench
+CONFIG_PATH=/etc/arvados/workbench
+DOC_URL="http://doc.arvados.org/install/install-workbench-app.html#configure"
diff --git a/build/rails-package-scripts/postinst.sh b/build/rails-package-scripts/postinst.sh
new file mode 100644 (file)
index 0000000..6fac26b
--- /dev/null
@@ -0,0 +1,251 @@
+#!/bin/sh
+# This code runs after package variable definitions and step2.sh.
+
+set -e
+
+DATABASE_READY=1
+APPLICATION_READY=1
+
+if [ -s "$HOME/.rvm/scripts/rvm" ] || [ -s "/usr/local/rvm/scripts/rvm" ]; then
+    COMMAND_PREFIX="/usr/local/rvm/bin/rvm-exec default"
+else
+    COMMAND_PREFIX=
+fi
+
+report_not_ready() {
+    local ready_flag="$1"; shift
+    local config_file="$1"; shift
+    if [ "1" != "$ready_flag" ]; then cat >&2 <<EOF
+
+PLEASE NOTE:
+
+The $PACKAGE_NAME package was not configured completely because
+$config_file needs some tweaking.
+Please refer to the documentation at
+<$DOC_URL> for more details.
+
+When $(basename "$config_file") has been modified,
+reconfigure or reinstall this package.
+
+EOF
+    fi
+}
+
+report_web_service_warning() {
+    local warning="$1"; shift
+    cat >&2 <<EOF
+
+WARNING: $warning.
+
+To override, set the WEB_SERVICE environment variable to the name of the service
+hosting the Rails server.
+
+For Debian-based systems, then reconfigure this package with dpkg-reconfigure.
+
+For RPM-based systems, then reinstall this package.
+
+EOF
+}
+
+run_and_report() {
+    # Usage: run_and_report ACTION_MSG CMD
+    # This is the usual wrapper that prints ACTION_MSG, runs CMD, then writes
+    # a message about whether CMD succeeded or failed.  Returns the exit code
+    # of CMD.
+    local action_message="$1"; shift
+    local retcode=0
+    echo -n "$action_message..."
+    if "$@"; then
+        echo " done."
+    else
+        retcode=$?
+        echo " failed."
+    fi
+    return $retcode
+}
+
+setup_confdirs() {
+    for confdir in "$@"; do
+        if [ ! -d "$confdir" ]; then
+            install -d -g "$WWW_OWNER" -m 0750 "$confdir"
+        fi
+    done
+}
+
+setup_conffile() {
+    # Usage: setup_conffile CONFFILE_PATH [SOURCE_PATH]
+    # Both paths are relative to RELEASE_CONFIG_PATH.
+    # This function will try to safely ensure that a symbolic link for
+    # the configuration file points from RELEASE_CONFIG_PATH to CONFIG_PATH.
+    # If SOURCE_PATH is given, this function will try to install that file as
+    # the configuration file in CONFIG_PATH, and return 1 if the file in
+    # CONFIG_PATH is unmodified from the source.
+    local conffile_relpath="$1"; shift
+    local conffile_source="$1"
+    local release_conffile="$RELEASE_CONFIG_PATH/$conffile_relpath"
+    local etc_conffile="$CONFIG_PATH/$(basename "$conffile_relpath")"
+
+    # Note that -h can return true and -e will return false simultaneously
+    # when the target is a dangling symlink.  We're okay with that outcome,
+    # so check -h first.
+    if [ ! -h "$release_conffile" ]; then
+        if [ ! -e "$release_conffile" ]; then
+            ln -s "$etc_conffile" "$release_conffile"
+        # If there's a config file in /var/www identical to the one in /etc,
+        # overwrite it with a symlink after porting its permissions.
+        elif cmp --quiet "$release_conffile" "$etc_conffile"; then
+            local ownership="$(stat -c "%u:%g" "$release_conffile")"
+            local owning_group="${ownership#*:}"
+            if [ 0 != "$owning_group" ]; then
+                chgrp "$owning_group" "$CONFIG_PATH" /etc/arvados
+            fi
+            chown "$ownership" "$etc_conffile"
+            chmod --reference="$release_conffile" "$etc_conffile"
+            ln --force -s "$etc_conffile" "$release_conffile"
+        fi
+    fi
+
+    if [ -n "$conffile_source" ]; then
+        if [ ! -e "$etc_conffile" ]; then
+            install -g "$WWW_OWNER" -m 0640 \
+                    "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"
+            return 1
+        # Even if $etc_conffile already existed, it might be unmodified from
+        # the source.  This is especially likely when a user installs, updates
+        # database.yml, then reconfigures before they update application.yml.
+        # Use cmp to be sure whether $etc_conffile is modified.
+        elif cmp --quiet "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"; then
+            return 1
+        fi
+    fi
+}
+
+prepare_database() {
+  DB_MIGRATE_STATUS=`$COMMAND_PREFIX bundle exec rake db:migrate:status 2>&1 || true`
+  if echo $DB_MIGRATE_STATUS | grep -qF 'Schema migrations table does not exist yet.'; then
+      # The database exists, but the migrations table doesn't.
+      run_and_report "Setting up database" $COMMAND_PREFIX bundle exec \
+                     rake "$RAILSPKG_DATABASE_LOAD_TASK" db:seed
+  elif echo $DB_MIGRATE_STATUS | grep -q '^database: '; then
+      run_and_report "Running db:migrate" \
+                     $COMMAND_PREFIX bundle exec rake db:migrate
+  elif echo $DB_MIGRATE_STATUS | grep -q 'database .* does not exist'; then
+      if ! run_and_report "Running db:setup" \
+           $COMMAND_PREFIX bundle exec rake db:setup 2>/dev/null; then
+          echo "Warning: unable to set up database." >&2
+          DATABASE_READY=0
+      fi
+  else
+    echo "Warning: Database is not ready to set up. Skipping database setup." >&2
+    DATABASE_READY=0
+  fi
+}
+
+configure_version() {
+  WEB_SERVICE=${WEB_SERVICE:-$(service --status-all 2>/dev/null \
+      | grep -Eo '\bnginx|httpd[^[:space:]]*' || true)}
+  if [ -z "$WEB_SERVICE" ]; then
+    report_web_service_warning "Web service (Nginx or Apache) not found"
+  elif [ "$WEB_SERVICE" != "$(echo "$WEB_SERVICE" | head -n 1)" ]; then
+    WEB_SERVICE=$(echo "$WEB_SERVICE" | head -n 1)
+    report_web_service_warning \
+        "Multiple web services found.  Choosing the first one ($WEB_SERVICE)"
+  fi
+
+  if [ -e /etc/redhat-release ]; then
+      # Recognize any service that starts with "nginx"; e.g., nginx16.
+      if [ "$WEB_SERVICE" != "${WEB_SERVICE#nginx}" ]; then
+        WWW_OWNER=nginx
+      else
+        WWW_OWNER=apache
+      fi
+  else
+      # Assume we're on a Debian-based system for now.
+      # Both Apache and Nginx run as www-data by default.
+      WWW_OWNER=www-data
+  fi
+
+  echo
+  echo "Assumption: $WEB_SERVICE is configured to serve Rails from"
+  echo "            $RELEASE_PATH"
+  echo "Assumption: $WEB_SERVICE and passenger run as $WWW_OWNER"
+  echo
+
+  echo -n "Creating symlinks to configuration in $CONFIG_PATH ..."
+  setup_confdirs /etc/arvados "$CONFIG_PATH"
+  setup_conffile environments/production.rb environments/production.rb.example \
+      || true
+  setup_conffile application.yml application.yml.example || APPLICATION_READY=0
+  if [ -n "$RAILSPKG_DATABASE_LOAD_TASK" ]; then
+      setup_conffile database.yml database.yml.example || DATABASE_READY=0
+  fi
+  setup_extra_conffiles
+  echo "... done."
+
+  # Before we do anything else, make sure some directories and files are in place
+  if [ ! -e $SHARED_PATH/log ]; then mkdir -p $SHARED_PATH/log; fi
+  if [ ! -e $RELEASE_PATH/tmp ]; then mkdir -p $RELEASE_PATH/tmp; fi
+  if [ ! -e $RELEASE_PATH/log ]; then ln -s $SHARED_PATH/log $RELEASE_PATH/log; fi
+  if [ ! -e $SHARED_PATH/log/production.log ]; then touch $SHARED_PATH/log/production.log; fi
+
+  cd "$RELEASE_PATH"
+  export RAILS_ENV=production
+
+  if ! $COMMAND_PREFIX bundle --version >/dev/null; then
+      run_and_report "Installing bundle" $COMMAND_PREFIX gem install bundle
+  fi
+
+  run_and_report "Running bundle install" \
+      $COMMAND_PREFIX bundle install --path $SHARED_PATH/vendor_bundle --local --quiet
+
+  echo -n "Ensuring directory and file permissions ..."
+  # Ensure correct ownership of a few files
+  chown "$WWW_OWNER:" $RELEASE_PATH/config/environment.rb
+  chown "$WWW_OWNER:" $RELEASE_PATH/config.ru
+  chown "$WWW_OWNER:" $RELEASE_PATH/Gemfile.lock
+  chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
+  chown -R "$WWW_OWNER:" $SHARED_PATH/log
+  case "$RAILSPKG_DATABASE_LOAD_TASK" in
+      db:schema:load) chown "$WWW_OWNER:" $RELEASE_PATH/db/schema.rb ;;
+      db:structure:load) chown "$WWW_OWNER:" $RELEASE_PATH/db/structure.sql ;;
+  esac
+  chmod 644 $SHARED_PATH/log/*
+  chmod -R 2775 $RELEASE_PATH/tmp
+  echo "... done."
+
+  if [ -n "$RAILSPKG_DATABASE_LOAD_TASK" ]; then
+      prepare_database
+  fi
+
+  if [ 11 = "$RAILSPKG_SUPPORTS_CONFIG_CHECK$APPLICATION_READY" ]; then
+      run_and_report "Checking application.yml for completeness" \
+          $COMMAND_PREFIX bundle exec rake config:check || APPLICATION_READY=0
+  fi
+
+  # precompile assets; thankfully this does not take long
+  if [ "$APPLICATION_READY" = "1" ]; then
+      run_and_report "Precompiling assets" \
+          $COMMAND_PREFIX bundle exec rake assets:precompile -q -s 2>/dev/null \
+          || APPLICATION_READY=0
+  else
+      echo "Precompiling assets... skipped."
+  fi
+  chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
+
+  setup_before_nginx_restart
+
+  if [ ! -z "$WEB_SERVICE" ]; then
+      service "$WEB_SERVICE" restart
+  fi
+}
+
+if [ "$1" = configure ]; then
+  # This is a debian-based system
+  configure_version
+elif [ "$1" = "0" ] || [ "$1" = "1" ] || [ "$1" = "2" ]; then
+  # This is an rpm-based system
+  configure_version
+fi
+
+report_not_ready "$DATABASE_READY" "$CONFIG_PATH/database.yml"
+report_not_ready "$APPLICATION_READY" "$CONFIG_PATH/application.yml"
diff --git a/build/rails-package-scripts/postrm.sh b/build/rails-package-scripts/postrm.sh
new file mode 100644 (file)
index 0000000..2d63f0b
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+# This code runs after package variable definitions and step2.sh.
+
+set -e
+
+purge () {
+  rm -rf $SHARED_PATH/vendor_bundle
+  rm -rf $SHARED_PATH/log
+  rm -rf $CONFIG_PATH
+  rmdir $SHARED_PATH || true
+  rmdir $INSTALL_PATH || true
+}
+
+if [ "$1" = 'purge' ]; then
+  # This is a debian-based system and purge was requested
+  purge
+elif [ "$1" = "0" ]; then
+  # This is an rpm-based system, no guarantees are made, always purge
+  # Apparently yum doesn't actually remember what it installed.
+  # Clean those files up here, then purge.
+  rm -rf $RELEASE_PATH
+  purge
+fi
diff --git a/build/rails-package-scripts/prerm.sh b/build/rails-package-scripts/prerm.sh
new file mode 100644 (file)
index 0000000..4ef5904
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+# This code runs after package variable definitions and step2.sh.
+
+remove () {
+  rm -f $RELEASE_PATH/config/database.yml
+  rm -f $RELEASE_PATH/config/environments/production.rb
+  rm -f $RELEASE_PATH/config/application.yml
+  # Old API server configuration file.
+  rm -f $RELEASE_PATH/config/initializers/omniauth.rb
+  rm -rf $RELEASE_PATH/public/assets/
+  rm -rf $RELEASE_PATH/tmp
+  rm -rf $RELEASE_PATH/.bundle
+  rm -rf $RELEASE_PATH/log
+}
+
+if [ "$1" = 'remove' ]; then
+  # This is a debian-based system and removal was requested
+  remove
+elif [ "$1" = "0" ] || [ "$1" = "1" ] || [ "$1" = "2" ]; then
+  # This is an rpm-based system
+  remove
+fi
diff --git a/build/rails-package-scripts/step2.sh b/build/rails-package-scripts/step2.sh
new file mode 100644 (file)
index 0000000..816b906
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+# This code runs after package variable definitions, before the actual
+# pre/post package work, to set some variable and function defaults.
+
+if [ -z "$INSTALL_PATH" ]; then
+    cat >&2 <<EOF
+
+PACKAGE BUILD ERROR: $0 is missing package metadata.
+
+This package is buggy.  Please mail <support@curoverse.com> to let
+us know the name and version number of the package you tried to
+install, and we'll get it fixed.
+
+EOF
+    exit 3
+fi
+
+RELEASE_PATH=$INSTALL_PATH/current
+RELEASE_CONFIG_PATH=$RELEASE_PATH/config
+SHARED_PATH=$INSTALL_PATH/shared
+
+RAILSPKG_SUPPORTS_CONFIG_CHECK=${RAILSPKG_SUPPORTS_CONFIG_CHECK:-1}
+if ! type setup_extra_conffiles >/dev/null 2>&1; then
+    setup_extra_conffiles() { return; }
+fi
+if ! type setup_before_nginx_restart >/dev/null 2>&1; then
+    setup_before_nginx_restart() { return; }
+fi
diff --git a/build/run-build-docker-images.sh b/build/run-build-docker-images.sh
new file mode 100755 (executable)
index 0000000..0a5841d
--- /dev/null
@@ -0,0 +1,167 @@
+#!/bin/bash
+
+function usage {
+    echo >&2
+    echo >&2 "usage: $0 [options]"
+    echo >&2
+    echo >&2 "$0 options:"
+    echo >&2 "  -t, --tags [csv_tags]         comma separated tags"
+    echo >&2 "  -u, --upload                  Upload the images (docker push)"
+    echo >&2 "  -h, --help                    Display this help and exit"
+    echo >&2
+    echo >&2 "  If no options are given, just builds the images."
+}
+
+upload=false
+
+# NOTE: This requires GNU getopt (part of the util-linux package on Debian-based distros).
+TEMP=`getopt -o hut: \
+    --long help,upload,tags: \
+    -n "$0" -- "$@"`
+
+if [ $? != 0 ] ; then echo "Use -h for help"; exit 1 ; fi
+# Note the quotes around `$TEMP': they are essential!
+eval set -- "$TEMP"
+
+while [ $# -ge 1 ]
+do
+    case $1 in
+        -u | --upload)
+            upload=true
+            shift
+            ;;
+        -t | --tags)
+            case "$2" in
+                "")
+                  echo "ERROR: --tags needs a parameter";
+                  usage;
+                  exit 1
+                  ;;
+                *)
+                  tags=$2;
+                  shift 2
+                  ;;
+            esac
+            ;;
+        --)
+            shift
+            break
+            ;;
+        *)
+            usage
+            exit 1
+            ;;
+    esac
+done
+
+
+EXITCODE=0
+
+COLUMNS=80
+
+title () {
+    printf "\n%*s\n\n" $(((${#title}+$COLUMNS)/2)) "********** $1 **********"
+}
+
+docker_push () {
+    if [[ ! -z "$tags" ]]
+    then
+        for tag in $( echo $tags|tr "," " " )
+        do
+             $DOCKER tag $1 $1:$tag
+        done
+    fi
+
+    # Sometimes docker push fails; retry it a few times if necessary.
+    for i in `seq 1 5`; do
+        $DOCKER push $*
+        ECODE=$?
+        if [[ "$ECODE" == "0" ]]; then
+            break
+        fi
+    done
+
+    if [[ "$ECODE" != "0" ]]; then
+        title "!!!!!! docker push $* failed !!!!!!"
+        EXITCODE=$(($EXITCODE + $ECODE))
+    fi
+}
+
+timer_reset() {
+    t0=$SECONDS
+}
+
+timer() {
+    echo -n "$(($SECONDS - $t0))s"
+}
+
+# Sanity check
+if ! [[ -n "$WORKSPACE" ]]; then
+    echo >&2
+    echo >&2 "Error: WORKSPACE environment variable not set"
+    echo >&2
+    exit 1
+fi
+
+echo $WORKSPACE
+
+# find the docker binary
+DOCKER=`which docker.io`
+
+if [[ "$DOCKER" == "" ]]; then
+    DOCKER=`which docker`
+fi
+
+if [[ "$DOCKER" == "" ]]; then
+    title "Error: you need to have docker installed. Could not find the docker executable."
+    exit 1
+fi
+
+# DOCKER
+title "Starting docker build"
+
+timer_reset
+
+# clean up the docker build environment
+cd "$WORKSPACE"
+
+tools/arvbox/bin/arvbox build dev
+ECODE=$?
+
+if [[ "$ECODE" != "0" ]]; then
+    title "!!!!!! docker BUILD FAILED !!!!!!"
+    EXITCODE=$(($EXITCODE + $ECODE))
+fi
+
+tools/arvbox/bin/arvbox build localdemo
+
+ECODE=$?
+
+if [[ "$ECODE" != "0" ]]; then
+    title "!!!!!! docker BUILD FAILED !!!!!!"
+    EXITCODE=$(($EXITCODE + $ECODE))
+fi
+
+title "docker build complete (`timer`)"
+
+title "uploading images"
+
+timer_reset
+
+if [[ "$ECODE" != "0" ]]; then
+    title "upload arvados images SKIPPED because build failed"
+else
+    if [[ $upload == true ]]; then 
+        ## 20150526 nico -- *sometimes* dockerhub needs re-login 
+        ## even though credentials are already in .dockercfg
+        docker login -u arvados
+
+        docker_push arvados/arvbox-dev
+        docker_push arvados/arvbox-demo
+        title "upload arvados images complete (`timer`)"
+    else
+        title "upload arvados images SKIPPED because no --upload option set"
+    fi
+fi
+
+exit $EXITCODE
diff --git a/build/run-build-docker-jobs-image.sh b/build/run-build-docker-jobs-image.sh
new file mode 100755 (executable)
index 0000000..fcf849b
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+function usage {
+    echo >&2
+    echo >&2 "usage: $0 [options]"
+    echo >&2
+    echo >&2 "$0 options:"
+    echo >&2 "  -t, --tags [csv_tags]         comma separated tags"
+    echo >&2 "  -u, --upload                  Upload the images (docker push)"
+    echo >&2 "  -h, --help                    Display this help and exit"
+    echo >&2
+    echo >&2 "  If no options are given, just builds the images."
+}
+
+upload=false
+
+# NOTE: This requires GNU getopt (part of the util-linux package on Debian-based distros).
+TEMP=`getopt -o hut: \
+    --long help,upload,tags: \
+    -n "$0" -- "$@"`
+
+if [ $? != 0 ] ; then echo "Use -h for help"; exit 1 ; fi
+# Note the quotes around `$TEMP': they are essential!
+eval set -- "$TEMP"
+
+while [ $# -ge 1 ]
+do
+    case $1 in
+        -u | --upload)
+            upload=true
+            shift
+            ;;
+        -t | --tags)
+            case "$2" in
+                "")
+                  echo "ERROR: --tags needs a parameter";
+                  usage;
+                  exit 1
+                  ;;
+                *)
+                  tags=$2;
+                  shift 2
+                  ;;
+            esac
+            ;;
+        --)
+            shift
+            break
+            ;;
+        *)
+            usage
+            exit 1
+            ;;
+    esac
+done
+
+
+EXITCODE=0
+
+COLUMNS=80
+
+title () {
+    printf "\n%*s\n\n" $(((${#title}+$COLUMNS)/2)) "********** $1 **********"
+}
+
+docker_push () {
+    if [[ ! -z "$tags" ]]
+    then
+        for tag in $( echo $tags|tr "," " " )
+        do
+             $DOCKER tag -f $1 $1:$tag
+        done
+    fi
+
+    # Sometimes docker push fails; retry it a few times if necessary.
+    for i in `seq 1 5`; do
+        $DOCKER push $*
+        ECODE=$?
+        if [[ "$ECODE" == "0" ]]; then
+            break
+        fi
+    done
+
+    if [[ "$ECODE" != "0" ]]; then
+        title "!!!!!! docker push $* failed !!!!!!"
+        EXITCODE=$(($EXITCODE + $ECODE))
+    fi
+}
+
+timer_reset() {
+    t0=$SECONDS
+}
+
+timer() {
+    echo -n "$(($SECONDS - $t0))s"
+}
+
+# Sanity check
+if ! [[ -n "$WORKSPACE" ]]; then
+    echo >&2
+    echo >&2 "Error: WORKSPACE environment variable not set"
+    echo >&2
+    exit 1
+fi
+
+echo $WORKSPACE
+
+# find the docker binary
+DOCKER=`which docker.io`
+
+if [[ "$DOCKER" == "" ]]; then
+    DOCKER=`which docker`
+fi
+
+if [[ "$DOCKER" == "" ]]; then
+    title "Error: you need to have docker installed. Could not find the docker executable."
+    exit 1
+fi
+
+# DOCKER
+title "Starting docker build"
+
+timer_reset
+
+# clean up the docker build environment
+cd "$WORKSPACE"
+cd docker
+rm -f jobs-image
+rm -f config.yml
+
+# Get test config.yml file
+cp $HOME/docker/config.yml .
+
+./build.sh jobs-image
+
+ECODE=$?
+
+if [[ "$ECODE" != "0" ]]; then
+    title "!!!!!! docker BUILD FAILED !!!!!!"
+    EXITCODE=$(($EXITCODE + $ECODE))
+fi
+
+title "docker build complete (`timer`)"
+
+title "uploading images"
+
+timer_reset
+
+if [[ "$ECODE" != "0" ]]; then
+    title "upload arvados images SKIPPED because build failed"
+else
+    if [[ $upload == true ]]; then 
+        ## 20150526 nico -- *sometimes* dockerhub needs re-login 
+        ## even though credentials are already in .dockercfg
+        docker login -u arvados
+
+        docker_push arvados/jobs
+        title "upload arvados images complete (`timer`)"
+    else
+        title "upload arvados images SKIPPED because no --upload option set"
+    fi
+fi
+
+exit $EXITCODE
diff --git a/build/run-build-packages-all-targets.sh b/build/run-build-packages-all-targets.sh
new file mode 100755 (executable)
index 0000000..f1a1e1c
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/bash
+
+read -rd "\000" helpmessage <<EOF
+$(basename $0): Orchestrate run-build-packages.sh for every target
+
+Syntax:
+        WORKSPACE=/path/to/arvados $(basename $0) [options]
+
+Options:
+
+--command
+    Build command to execute (default: use built-in Docker image command)
+--test-packages
+    Run package install tests
+--debug
+    Output debug information (default: false)
+
+WORKSPACE=path         Path to the Arvados source tree to build packages from
+
+EOF
+
+if ! [[ -n "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: WORKSPACE environment variable not set"
+  echo >&2
+  exit 1
+fi
+
+if ! [[ -d "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: $WORKSPACE is not a directory"
+  echo >&2
+  exit 1
+fi
+
+set -e
+
+PARSEDOPTS=$(getopt --name "$0" --longoptions \
+    help,test-packages,debug,command:,only-test: \
+    -- "" "$@")
+if [ $? -ne 0 ]; then
+    exit 1
+fi
+
+COMMAND=
+DEBUG=
+TEST_PACKAGES=
+ONLY_TEST=
+
+eval set -- "$PARSEDOPTS"
+while [ $# -gt 0 ]; do
+    case "$1" in
+        --help)
+            echo >&2 "$helpmessage"
+            echo >&2
+            exit 1
+            ;;
+        --debug)
+            DEBUG="--debug"
+            ;;
+        --command)
+            COMMAND="$2"; shift
+            ;;
+        --test-packages)
+            TEST_PACKAGES="--test-packages"
+            ;;
+        --only-test)
+            ONLY_TEST="$1 $2"; shift
+            ;;
+        --)
+            if [ $# -gt 1 ]; then
+                echo >&2 "$0: unrecognized argument '$2'. Try: $0 --help"
+                exit 1
+            fi
+            ;;
+    esac
+    shift
+done
+
+cd $(dirname $0)
+
+FINAL_EXITCODE=0
+
+for dockerfile_path in $(find -name Dockerfile); do
+    if ./run-build-packages-one-target.sh --target "$(basename $(dirname "$dockerfile_path"))" --command "$COMMAND" $DEBUG $TEST_PACKAGES $ONLY_TEST ; then
+        true
+    else
+        FINAL_EXITCODE=$?
+    fi
+done
+
+if test $FINAL_EXITCODE != 0 ; then
+    echo "Build packages failed with code $FINAL_EXITCODE" >&2
+fi
+
+exit $FINAL_EXITCODE
diff --git a/build/run-build-packages-one-target.sh b/build/run-build-packages-one-target.sh
new file mode 100755 (executable)
index 0000000..c5e0a89
--- /dev/null
@@ -0,0 +1,203 @@
+#!/bin/bash
+
+read -rd "\000" helpmessage <<EOF
+$(basename $0): Orchestrate run-build-packages.sh for one target
+
+Syntax:
+        WORKSPACE=/path/to/arvados $(basename $0) [options]
+
+--target <target>
+    Distribution to build packages for (default: debian7)
+--command
+    Build command to execute (default: use built-in Docker image command)
+--test-packages
+    Run package install test script "test-packages-$target.sh"
+--debug
+    Output debug information (default: false)
+--only-test
+    Test only a specific package
+
+WORKSPACE=path         Path to the Arvados source tree to build packages from
+
+EOF
+
+set -e
+
+if ! [[ -n "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: WORKSPACE environment variable not set"
+  echo >&2
+  exit 1
+fi
+
+if ! [[ -d "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: $WORKSPACE is not a directory"
+  echo >&2
+  exit 1
+fi
+
+PARSEDOPTS=$(getopt --name "$0" --longoptions \
+    help,debug,test-packages,target:,command:,only-test: \
+    -- "" "$@")
+if [ $? -ne 0 ]; then
+    exit 1
+fi
+
+TARGET=debian7
+COMMAND=
+DEBUG=
+
+eval set -- "$PARSEDOPTS"
+while [ $# -gt 0 ]; do
+    case "$1" in
+        --help)
+            echo >&2 "$helpmessage"
+            echo >&2
+            exit 1
+            ;;
+        --target)
+            TARGET="$2"; shift
+            ;;
+        --only-test)
+            packages="$2"; shift
+            ;;
+        --debug)
+            DEBUG=" --debug"
+            ;;
+        --command)
+            COMMAND="$2"; shift
+            ;;
+        --test-packages)
+            test_packages=1
+            ;;
+        --)
+            if [ $# -gt 1 ]; then
+                echo >&2 "$0: unrecognized argument '$2'. Try: $0 --help"
+                exit 1
+            fi
+            ;;
+    esac
+    shift
+done
+
+set -e
+
+if [[ -n "$test_packages" ]]; then
+    if [[ -n "$(find $WORKSPACE/packages/$TARGET -name *.rpm)" ]] ; then
+        createrepo $WORKSPACE/packages/$TARGET
+    fi
+
+    if [[ -n "$(find $WORKSPACE/packages/$TARGET -name *.deb)" ]] ; then
+        (cd $WORKSPACE/packages/$TARGET
+         dpkg-scanpackages .  2> >(grep -v 'warning' 1>&2) | gzip -c > Packages.gz
+        )
+    fi
+
+    COMMAND="/jenkins/package-testing/test-packages-$TARGET.sh"
+    IMAGE="arvados/package-test:$TARGET"
+else
+    IMAGE="arvados/build:$TARGET"
+    if [[ "$COMMAND" != "" ]]; then
+        COMMAND="/usr/local/rvm/bin/rvm-exec default bash /jenkins/$COMMAND --target $TARGET$DEBUG"
+    fi
+fi
+
+JENKINS_DIR=$(dirname "$(readlink -e "$0")")
+
+if [[ -n "$test_packages" ]]; then
+    pushd "$JENKINS_DIR/package-test-dockerfiles"
+else
+    pushd "$JENKINS_DIR/package-build-dockerfiles"
+    make "$TARGET/generated"
+fi
+
+echo $TARGET
+cd $TARGET
+time docker build --tag=$IMAGE .
+popd
+
+if test -z "$packages" ; then
+    packages="arvados-api-server
+        arvados-data-manager
+        arvados-docker-cleaner
+        arvados-git-httpd
+        arvados-node-manager
+        arvados-src
+        arvados-workbench
+        crunchstat
+        keepproxy
+        keep-rsync
+        keepstore
+        keep-web
+        libarvados-perl"
+
+    case "$TARGET" in
+        centos6)
+            packages="$packages python27-python-arvados-fuse
+                  python27-python-arvados-python-client"
+            ;;
+        *)
+            packages="$packages python-arvados-fuse
+                  python-arvados-python-client"
+            ;;
+    esac
+fi
+
+FINAL_EXITCODE=0
+
+package_fails=""
+
+mkdir -p "$WORKSPACE/apps/workbench/vendor/cache-$TARGET"
+mkdir -p "$WORKSPACE/services/api/vendor/cache-$TARGET"
+
+docker_volume_args=(
+    -v "$JENKINS_DIR:/jenkins"
+    -v "$WORKSPACE:/arvados"
+    -v /arvados/services/api/vendor/bundle
+    -v /arvados/apps/workbench/vendor/bundle
+    -v "$WORKSPACE/services/api/vendor/cache-$TARGET:/arvados/services/api/vendor/cache"
+    -v "$WORKSPACE/apps/workbench/vendor/cache-$TARGET:/arvados/apps/workbench/vendor/cache"
+)
+
+if [[ -n "$test_packages" ]]; then
+    for p in $packages ; do
+        echo
+        echo "START: $p test on $IMAGE" >&2
+        if docker run --rm \
+            "${docker_volume_args[@]}" \
+            --env ARVADOS_DEBUG=1 \
+            --env "TARGET=$TARGET" \
+            --env "WORKSPACE=/arvados" \
+            "$IMAGE" $COMMAND $p
+        then
+            echo "OK: $p test on $IMAGE succeeded" >&2
+        else
+            FINAL_EXITCODE=$?
+            package_fails="$package_fails $p"
+            echo "ERROR: $p test on $IMAGE failed with exit status $FINAL_EXITCODE" >&2
+        fi
+    done
+else
+    echo
+    echo "START: build packages on $IMAGE" >&2
+    if docker run --rm \
+        "${docker_volume_args[@]}" \
+        --env ARVADOS_DEBUG=1 \
+        "$IMAGE" $COMMAND
+    then
+        echo
+        echo "OK: build packages on $IMAGE succeeded" >&2
+    else
+        FINAL_EXITCODE=$?
+        echo "ERROR: build packages on $IMAGE failed with exit status $FINAL_EXITCODE" >&2
+    fi
+fi
+
+if test -n "$package_fails" ; then
+    echo "Failed package tests:$package_fails" >&2
+fi
+
+exit $FINAL_EXITCODE
diff --git a/build/run-build-packages-sso.sh b/build/run-build-packages-sso.sh
new file mode 100755 (executable)
index 0000000..cc673a6
--- /dev/null
@@ -0,0 +1,161 @@
+#!/bin/bash
+
+JENKINS_DIR=$(dirname $(readlink -e "$0"))
+. "$JENKINS_DIR/run-library.sh"
+
+read -rd "\000" helpmessage <<EOF
+$(basename $0): Build Arvados SSO server package
+
+Syntax:
+        WORKSPACE=/path/to/arvados-sso $(basename $0) [options]
+
+Options:
+
+--debug
+    Output debug information (default: false)
+--target
+    Distribution to build packages for (default: debian7)
+
+WORKSPACE=path         Path to the Arvados SSO source tree to build packages from
+
+EOF
+
+EXITCODE=0
+DEBUG=${ARVADOS_DEBUG:-0}
+TARGET=debian7
+
+PARSEDOPTS=$(getopt --name "$0" --longoptions \
+    help,build-bundle-packages,debug,target: \
+    -- "" "$@")
+if [ $? -ne 0 ]; then
+    exit 1
+fi
+
+eval set -- "$PARSEDOPTS"
+while [ $# -gt 0 ]; do
+    case "$1" in
+        --help)
+            echo >&2 "$helpmessage"
+            echo >&2
+            exit 1
+            ;;
+        --target)
+            TARGET="$2"; shift
+            ;;
+        --debug)
+            DEBUG=1
+            ;;
+        --test-packages)
+            test_packages=1
+            ;;
+        --)
+            if [ $# -gt 1 ]; then
+                echo >&2 "$0: unrecognized argument '$2'. Try: $0 --help"
+                exit 1
+            fi
+            ;;
+    esac
+    shift
+done
+
+STDOUT_IF_DEBUG=/dev/null
+STDERR_IF_DEBUG=/dev/null
+DASHQ_UNLESS_DEBUG=-q
+if [[ "$DEBUG" != 0 ]]; then
+    STDOUT_IF_DEBUG=/dev/stdout
+    STDERR_IF_DEBUG=/dev/stderr
+    DASHQ_UNLESS_DEBUG=
+fi
+
+case "$TARGET" in
+    debian7)
+        FORMAT=deb
+        ;;
+    debian8)
+        FORMAT=deb
+        ;;
+    ubuntu1204)
+        FORMAT=deb
+        ;;
+    ubuntu1404)
+        FORMAT=deb
+        ;;
+    centos6)
+        FORMAT=rpm
+        ;;
+    *)
+        echo -e "$0: Unknown target '$TARGET'.\n" >&2
+        exit 1
+        ;;
+esac
+
+if ! [[ -n "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: WORKSPACE environment variable not set"
+  echo >&2
+  exit 1
+fi
+
+if ! [[ -d "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: $WORKSPACE is not a directory"
+  echo >&2
+  exit 1
+fi
+
+# Test for fpm
+fpm --version >/dev/null 2>&1
+
+if [[ "$?" != 0 ]]; then
+    echo >&2 "$helpmessage"
+    echo >&2
+    echo >&2 "Error: fpm not found"
+    echo >&2
+    exit 1
+fi
+
+RUN_BUILD_PACKAGES_PATH="`dirname \"$0\"`"
+RUN_BUILD_PACKAGES_PATH="`( cd \"$RUN_BUILD_PACKAGES_PATH\" && pwd )`"  # absolutized and normalized
+if [ -z "$RUN_BUILD_PACKAGES_PATH" ] ; then
+    # error; for some reason, the path is not accessible
+    # to the script (e.g. permissions re-evaled after suid)
+    exit 1  # fail
+fi
+
+debug_echo "$0 is running from $RUN_BUILD_PACKAGES_PATH"
+debug_echo "Workspace is $WORKSPACE"
+
+if [[ -f /etc/profile.d/rvm.sh ]]; then
+    source /etc/profile.d/rvm.sh
+    GEM="rvm-exec default gem"
+else
+    GEM=gem
+fi
+
+# Make all files world-readable -- jenkins runs with umask 027, and has checked
+# out our git tree here
+chmod o+r "$WORKSPACE" -R
+
+# More cleanup - make sure all executables that we'll package are 755
+# No executables in the sso server package
+#find -type d -name 'bin' |xargs -I {} find {} -type f |xargs -I {} chmod 755 {}
+
+# Now fix our umask to something better suited to building and publishing
+# gems and packages
+umask 0022
+
+debug_echo "umask is" `umask`
+
+if [[ ! -d "$WORKSPACE/packages/$TARGET" ]]; then
+    mkdir -p "$WORKSPACE/packages/$TARGET"
+fi
+
+# Build the SSO server package
+handle_rails_package arvados-sso-server "$WORKSPACE" \
+                     "$WORKSPACE/LICENCE" --url="https://arvados.org" \
+                     --description="Arvados SSO server - Arvados is a free and open source platform for big data science." \
+                     --license="Expat license"
+
+exit $EXITCODE
diff --git a/build/run-build-packages.sh b/build/run-build-packages.sh
new file mode 100755 (executable)
index 0000000..6970929
--- /dev/null
@@ -0,0 +1,543 @@
+#!/bin/bash
+
+. `dirname "$(readlink -f "$0")"`/run-library.sh
+. `dirname "$(readlink -f "$0")"`/libcloud-pin
+
+read -rd "\000" helpmessage <<EOF
+$(basename $0): Build Arvados packages
+
+Syntax:
+        WORKSPACE=/path/to/arvados $(basename $0) [options]
+
+Options:
+
+--build-bundle-packages  (default: false)
+    Build api server and workbench packages with vendor/bundle included
+--debug
+    Output debug information (default: false)
+--target
+    Distribution to build packages for (default: debian7)
+--command
+    Build command to execute (defaults to the run command defined in the
+    Docker image)
+
+WORKSPACE=path         Path to the Arvados source tree to build packages from
+
+EOF
+
+EXITCODE=0
+DEBUG=${ARVADOS_DEBUG:-0}
+TARGET=debian7
+COMMAND=
+
+PARSEDOPTS=$(getopt --name "$0" --longoptions \
+    help,build-bundle-packages,debug,target: \
+    -- "" "$@")
+if [ $? -ne 0 ]; then
+    exit 1
+fi
+
+eval set -- "$PARSEDOPTS"
+while [ $# -gt 0 ]; do
+    case "$1" in
+        --help)
+            echo >&2 "$helpmessage"
+            echo >&2
+            exit 1
+            ;;
+        --target)
+            TARGET="$2"; shift
+            ;;
+        --debug)
+            DEBUG=1
+            ;;
+        --command)
+            COMMAND="$2"; shift
+            ;;
+        --)
+            if [ $# -gt 1 ]; then
+                echo >&2 "$0: unrecognized argument '$2'. Try: $0 --help"
+                exit 1
+            fi
+            ;;
+    esac
+    shift
+done
+
+if [[ "$COMMAND" != "" ]]; then
+  COMMAND="/usr/local/rvm/bin/rvm-exec default bash /jenkins/$COMMAND --target $TARGET"
+fi
+
+STDOUT_IF_DEBUG=/dev/null
+STDERR_IF_DEBUG=/dev/null
+DASHQ_UNLESS_DEBUG=-q
+if [[ "$DEBUG" != 0 ]]; then
+    STDOUT_IF_DEBUG=/dev/stdout
+    STDERR_IF_DEBUG=/dev/stderr
+    DASHQ_UNLESS_DEBUG=
+fi
+
+declare -a PYTHON_BACKPORTS PYTHON3_BACKPORTS
+
+PYTHON2_VERSION=2.7
+PYTHON3_VERSION=$(python3 -c 'import sys; print("{v.major}.{v.minor}".format(v=sys.version_info))')
+
+case "$TARGET" in
+    debian7)
+        FORMAT=deb
+        PYTHON2_PACKAGE=python$PYTHON2_VERSION
+        PYTHON2_PKG_PREFIX=python
+        PYTHON3_PACKAGE=python$PYTHON3_VERSION
+        PYTHON3_PKG_PREFIX=python3
+        PYTHON_BACKPORTS=(python-gflags pyvcf google-api-python-client==1.4.2 \
+            oauth2client==1.5.2 pyasn1==0.1.7 pyasn1-modules==0.0.5 \
+            rsa uritemplate httplib2 ws4py pykka six pyexecjs jsonschema \
+            ciso8601 pycrypto backports.ssl_match_hostname llfuse==0.41.1 \
+            'pycurl<7.21.5' contextlib2)
+        PYTHON3_BACKPORTS=(docker-py six requests websocket-client)
+        ;;
+    debian8)
+        FORMAT=deb
+        PYTHON2_PACKAGE=python$PYTHON2_VERSION
+        PYTHON2_PKG_PREFIX=python
+        PYTHON3_PACKAGE=python$PYTHON3_VERSION
+        PYTHON3_PKG_PREFIX=python3
+        PYTHON_BACKPORTS=(python-gflags pyvcf google-api-python-client==1.4.2 \
+            oauth2client==1.5.2 pyasn1==0.1.7 pyasn1-modules==0.0.5 \
+            rsa uritemplate httplib2 ws4py pykka six pyexecjs jsonschema \
+            ciso8601 pycrypto backports.ssl_match_hostname llfuse==0.41.1 \
+            'pycurl<7.21.5')
+        PYTHON3_BACKPORTS=(docker-py six requests websocket-client)
+        ;;
+    ubuntu1204)
+        FORMAT=deb
+        PYTHON2_PACKAGE=python$PYTHON2_VERSION
+        PYTHON2_PKG_PREFIX=python
+        PYTHON3_PACKAGE=python$PYTHON3_VERSION
+        PYTHON3_PKG_PREFIX=python3
+        PYTHON_BACKPORTS=(python-gflags pyvcf google-api-python-client==1.4.2 \
+            oauth2client==1.5.2 pyasn1==0.1.7 pyasn1-modules==0.0.5 \
+            rsa uritemplate httplib2 ws4py pykka six pyexecjs jsonschema \
+            ciso8601 pycrypto backports.ssl_match_hostname llfuse==0.41.1 \
+            contextlib2 \
+            'pycurl<7.21.5')
+        PYTHON3_BACKPORTS=(docker-py six requests websocket-client)
+        ;;
+    ubuntu1404)
+        FORMAT=deb
+        PYTHON2_PACKAGE=python$PYTHON2_VERSION
+        PYTHON2_PKG_PREFIX=python
+        PYTHON3_PACKAGE=python$PYTHON3_VERSION
+        PYTHON3_PKG_PREFIX=python3
+        PYTHON_BACKPORTS=(pyasn1==0.1.7 pyvcf pyasn1-modules==0.0.5 llfuse==0.41.1 ciso8601 \
+            google-api-python-client==1.4.2 six uritemplate oauth2client==1.5.2 httplib2 \
+            rsa 'pycurl<7.21.5' backports.ssl_match_hostname)
+        PYTHON3_BACKPORTS=(docker-py requests websocket-client)
+        ;;
+    centos6)
+        FORMAT=rpm
+        PYTHON2_PACKAGE=$(rpm -qf "$(which python$PYTHON2_VERSION)" --queryformat '%{NAME}\n')
+        PYTHON2_PKG_PREFIX=$PYTHON2_PACKAGE
+        PYTHON3_PACKAGE=$(rpm -qf "$(which python$PYTHON3_VERSION)" --queryformat '%{NAME}\n')
+        PYTHON3_PKG_PREFIX=$PYTHON3_PACKAGE
+        PYTHON_BACKPORTS=(python-gflags pyvcf google-api-python-client==1.4.2 \
+            oauth2client==1.5.2 pyasn1==0.1.7 pyasn1-modules==0.0.5 \
+            rsa uritemplate httplib2 ws4py pykka six pyexecjs jsonschema \
+            ciso8601 pycrypto backports.ssl_match_hostname 'pycurl<7.21.5' \
+            python-daemon lockfile llfuse==0.41.1 'pbr<1.0')
+        PYTHON3_BACKPORTS=(docker-py six requests websocket-client)
+        export PYCURL_SSL_LIBRARY=nss
+        ;;
+    *)
+        echo -e "$0: Unknown target '$TARGET'.\n" >&2
+        exit 1
+        ;;
+esac
+
+
+if ! [[ -n "$WORKSPACE" ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: WORKSPACE environment variable not set"
+  echo >&2
+  exit 1
+fi
+
+# Test for fpm
+fpm --version >/dev/null 2>&1
+
+if [[ "$?" != 0 ]]; then
+  echo >&2 "$helpmessage"
+  echo >&2
+  echo >&2 "Error: fpm not found"
+  echo >&2
+  exit 1
+fi
+
+EASY_INSTALL2=$(find_easy_install -$PYTHON2_VERSION "")
+EASY_INSTALL3=$(find_easy_install -$PYTHON3_VERSION 3)
+
+RUN_BUILD_PACKAGES_PATH="`dirname \"$0\"`"
+RUN_BUILD_PACKAGES_PATH="`( cd \"$RUN_BUILD_PACKAGES_PATH\" && pwd )`"  # absolutized and normalized
+if [ -z "$RUN_BUILD_PACKAGES_PATH" ] ; then
+  # error; for some reason, the path is not accessible
+  # to the script (e.g. permissions re-evaled after suid)
+  exit 1  # fail
+fi
+
+debug_echo "$0 is running from $RUN_BUILD_PACKAGES_PATH"
+debug_echo "Workspace is $WORKSPACE"
+
+if [[ -f /etc/profile.d/rvm.sh ]]; then
+    source /etc/profile.d/rvm.sh
+    GEM="rvm-exec default gem"
+else
+    GEM=gem
+fi
+
+# Make all files world-readable -- jenkins runs with umask 027, and has checked
+# out our git tree here
+chmod o+r "$WORKSPACE" -R
+
+# More cleanup - make sure all executables that we'll package are 755
+find -type d -name 'bin' |xargs -I {} find {} -type f |xargs -I {} chmod 755 {}
+
+# Now fix our umask to something better suited to building and publishing
+# gems and packages
+umask 0022
+
+debug_echo "umask is" `umask`
+
+if [[ ! -d "$WORKSPACE/packages/$TARGET" ]]; then
+  mkdir -p $WORKSPACE/packages/$TARGET
+fi
+
+# Perl packages
+debug_echo -e "\nPerl packages\n"
+
+cd "$WORKSPACE/sdk/perl"
+
+if [[ -e Makefile ]]; then
+  make realclean >"$STDOUT_IF_DEBUG"
+fi
+find -maxdepth 1 \( -name 'MANIFEST*' -or -name "libarvados-perl*.$FORMAT" \) \
+    -delete
+rm -rf install
+
+perl Makefile.PL INSTALL_BASE=install >"$STDOUT_IF_DEBUG" && \
+    make install INSTALLDIRS=perl >"$STDOUT_IF_DEBUG" && \
+    fpm_build install/lib/=/usr/share libarvados-perl \
+    "Curoverse, Inc." dir "$(version_from_git)" install/man/=/usr/share/man \
+    "$WORKSPACE/LICENSE-2.0.txt=/usr/share/doc/libarvados-perl/LICENSE-2.0.txt" && \
+    mv --no-clobber libarvados-perl*.$FORMAT "$WORKSPACE/packages/$TARGET/"
+
+# Ruby gems
+debug_echo -e "\nRuby gems\n"
+
+FPM_GEM_PREFIX=$($GEM environment gemdir)
+
+cd "$WORKSPACE/sdk/ruby"
+handle_ruby_gem arvados
+
+cd "$WORKSPACE/sdk/cli"
+handle_ruby_gem arvados-cli
+
+cd "$WORKSPACE/services/login-sync"
+handle_ruby_gem arvados-login-sync
+
+# Python packages
+debug_echo -e "\nPython packages\n"
+
+cd "$WORKSPACE/sdk/pam"
+handle_python_package
+
+cd "$WORKSPACE/sdk/python"
+handle_python_package
+
+cd "$WORKSPACE/sdk/cwl"
+handle_python_package
+
+cd "$WORKSPACE/services/fuse"
+handle_python_package
+
+cd "$WORKSPACE/services/nodemanager"
+handle_python_package
+
+# arvados-src
+(
+    set -e
+
+    cd "$WORKSPACE"
+    COMMIT_HASH=$(format_last_commit_here "%H")
+
+    SRC_BUILD_DIR=$(mktemp -d)
+    # mktemp creates the directory with 0700 permissions by default
+    chmod 755 $SRC_BUILD_DIR
+    git clone $DASHQ_UNLESS_DEBUG "$WORKSPACE/.git" "$SRC_BUILD_DIR"
+    cd "$SRC_BUILD_DIR"
+
+    # go into detached-head state
+    git checkout $DASHQ_UNLESS_DEBUG "$COMMIT_HASH"
+    echo "$COMMIT_HASH" >git-commit.version
+
+    cd "$SRC_BUILD_DIR"
+    PKG_VERSION=$(version_from_git)
+    cd $WORKSPACE/packages/$TARGET
+    fpm_build $SRC_BUILD_DIR/=/usr/local/arvados/src arvados-src 'Curoverse, Inc.' 'dir' "$PKG_VERSION" "--exclude=usr/local/arvados/src/.git" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=The Arvados source code" "--architecture=all"
+
+    rm -rf "$SRC_BUILD_DIR"
+)
+
+# On older platforms we need to publish a backport of libfuse >=2.9.2,
+# and we need to build and install it here in order to even build an
+# llfuse package.
+cd $WORKSPACE/packages/$TARGET
+if [[ $TARGET =~ ubuntu1204 ]]; then
+    # port libfuse 2.9.2 to Ubuntu 12.04
+    LIBFUSE_DIR=$(mktemp -d)
+    (
+        cd $LIBFUSE_DIR
+        # download fuse 2.9.2 ubuntu 14.04 source package
+        file="fuse_2.9.2.orig.tar.xz" && curl -L -o "${file}" "http://archive.ubuntu.com/ubuntu/pool/main/f/fuse/${file}"
+        file="fuse_2.9.2-4ubuntu4.14.04.1.debian.tar.xz" && curl -L -o "${file}" "http://archive.ubuntu.com/ubuntu/pool/main/f/fuse/${file}"
+        file="fuse_2.9.2-4ubuntu4.14.04.1.dsc" && curl -L -o "${file}" "http://archive.ubuntu.com/ubuntu/pool/main/f/fuse/${file}"
+
+        # install dpkg-source and dpkg-buildpackage commands
+        apt-get install -y --no-install-recommends dpkg-dev
+
+        # extract source and apply patches
+        dpkg-source -x fuse_2.9.2-4ubuntu4.14.04.1.dsc
+        rm -f fuse_2.9.2.orig.tar.xz fuse_2.9.2-4ubuntu4.14.04.1.debian.tar.xz fuse_2.9.2-4ubuntu4.14.04.1.dsc
+
+        # add new version to changelog
+        cd fuse-2.9.2
+        (
+            echo "fuse (2.9.2-5) precise; urgency=low"
+            echo
+            echo "  * Backported from trusty-security to precise"
+            echo
+            echo " -- Joshua Randall <jcrandall@alum.mit.edu>  Thu, 4 Feb 2016 11:31:00 -0000"
+            echo
+            cat debian/changelog
+        ) > debian/changelog.new
+        mv debian/changelog.new debian/changelog
+
+        # install build-deps and build
+        apt-get install -y --no-install-recommends debhelper dh-autoreconf libselinux-dev
+        dpkg-buildpackage -rfakeroot -b
+    )
+    fpm_build "$LIBFUSE_DIR/fuse_2.9.2-5_amd64.deb" fuse "Ubuntu Developers" deb "2.9.2" --iteration 5
+    fpm_build "$LIBFUSE_DIR/libfuse2_2.9.2-5_amd64.deb" libfuse2 "Ubuntu Developers" deb "2.9.2" --iteration 5
+    fpm_build "$LIBFUSE_DIR/libfuse-dev_2.9.2-5_amd64.deb" libfuse-dev "Ubuntu Developers" deb "2.9.2" --iteration 5
+    dpkg -i \
+        "$WORKSPACE/packages/$TARGET/fuse_2.9.2-5_amd64.deb" \
+        "$WORKSPACE/packages/$TARGET/libfuse2_2.9.2-5_amd64.deb" \
+        "$WORKSPACE/packages/$TARGET/libfuse-dev_2.9.2-5_amd64.deb"
+    apt-get -y --no-install-recommends -f install
+    rm -rf $LIBFUSE_DIR
+elif [[ $TARGET =~ centos6 ]]; then
+    # port fuse 2.9.2 to centos 6
+    # install tools to build rpm from source
+    yum install -y rpm-build redhat-rpm-config
+    LIBFUSE_DIR=$(mktemp -d)
+    (
+        cd "$LIBFUSE_DIR"
+        # download fuse 2.9.2 centos 7 source rpm
+        file="fuse-2.9.2-6.el7.src.rpm" && curl -L -o "${file}" "http://vault.centos.org/7.2.1511/os/Source/SPackages/${file}"
+        (
+            # modify source rpm spec to remove conflict on filesystem version
+            mkdir -p /root/rpmbuild/SOURCES
+            cd /root/rpmbuild/SOURCES
+            rpm2cpio ${LIBFUSE_DIR}/fuse-2.9.2-6.el7.src.rpm | cpio -i
+            perl -pi -e 's/Conflicts:\s*filesystem.*//g' fuse.spec
+        )
+        # build rpms from source 
+        rpmbuild -bb /root/rpmbuild/SOURCES/fuse.spec
+        rm -f fuse-2.9.2-6.el7.src.rpm
+        # move built RPMs to LIBFUSE_DIR
+        mv "/root/rpmbuild/RPMS/x86_64/fuse-2.9.2-6.el6.x86_64.rpm" ${LIBFUSE_DIR}/
+        mv "/root/rpmbuild/RPMS/x86_64/fuse-libs-2.9.2-6.el6.x86_64.rpm" ${LIBFUSE_DIR}/
+        mv "/root/rpmbuild/RPMS/x86_64/fuse-devel-2.9.2-6.el6.x86_64.rpm" ${LIBFUSE_DIR}/
+        rm -rf /root/rpmbuild
+    )
+    fpm_build "$LIBFUSE_DIR/fuse-libs-2.9.2-6.el6.x86_64.rpm" fuse-libs "Centos Developers" rpm "2.9.2" --iteration 5
+    fpm_build "$LIBFUSE_DIR/fuse-2.9.2-6.el6.x86_64.rpm" fuse "Centos Developers" rpm "2.9.2" --iteration 5 --no-auto-depends
+    fpm_build "$LIBFUSE_DIR/fuse-devel-2.9.2-6.el6.x86_64.rpm" fuse-devel "Centos Developers" rpm "2.9.2" --iteration 5 --no-auto-depends
+    yum install -y \
+        "$WORKSPACE/packages/$TARGET/fuse-libs-2.9.2-5.x86_64.rpm" \
+        "$WORKSPACE/packages/$TARGET/fuse-2.9.2-5.x86_64.rpm" \
+        "$WORKSPACE/packages/$TARGET/fuse-devel-2.9.2-5.x86_64.rpm"
+fi
+
+# Go binaries
+cd $WORKSPACE/packages/$TARGET
+export GOPATH=$(mktemp -d)
+package_go_binary services/keepstore keepstore \
+    "Keep storage daemon, accessible to clients on the LAN"
+package_go_binary services/keepproxy keepproxy \
+    "Make a Keep cluster accessible to clients that are not on the LAN"
+package_go_binary services/keep-web keep-web \
+    "Static web hosting service for user data stored in Arvados Keep"
+package_go_binary services/datamanager arvados-data-manager \
+    "Ensure block replication levels, report disk usage, and determine which blocks should be deleted when space is needed"
+package_go_binary services/arv-git-httpd arvados-git-httpd \
+    "Provide authenticated http access to Arvados-hosted git repositories"
+package_go_binary services/crunchstat crunchstat \
+    "Gather cpu/memory/network statistics of running Crunch jobs"
+package_go_binary tools/keep-rsync keep-rsync \
+    "Copy all data from one set of Keep servers to another"
+
+# The Python SDK
+# Please resist the temptation to add --no-python-fix-name to the fpm call here
+# (which would remove the python- prefix from the package name), because this
+# package is a dependency of arvados-fuse, and fpm can not omit the python-
+# prefix from only one of the dependencies of a package...  Maybe I could
+# whip up a patch and send it upstream, but that will be for another day. Ward,
+# 2014-05-15
+cd $WORKSPACE/packages/$TARGET
+rm -rf "$WORKSPACE/sdk/python/build"
+fpm_build $WORKSPACE/sdk/python "${PYTHON2_PKG_PREFIX}-arvados-python-client" 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/sdk/python/arvados_python_client.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados Python SDK" --deb-recommends=git
+
+# cwl-runner
+cd $WORKSPACE/packages/$TARGET
+rm -rf "$WORKSPACE/sdk/cwl/build"
+fpm_build $WORKSPACE/sdk/cwl "${PYTHON2_PKG_PREFIX}-arvados-cwl-runner" 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/sdk/cwl/arvados_cwl_runner.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados CWL runner"
+
+# The PAM module
+if [[ $TARGET =~ debian|ubuntu ]]; then
+    cd $WORKSPACE/packages/$TARGET
+    rm -rf "$WORKSPACE/sdk/pam/build"
+    fpm_build $WORKSPACE/sdk/pam libpam-arvados 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/sdk/pam/arvados_pam.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=PAM module for authenticating shell logins using Arvados API tokens" --depends libpam-python
+fi
+
+# The FUSE driver
+# Please see comment about --no-python-fix-name above; we stay consistent and do
+# not omit the python- prefix first.
+cd $WORKSPACE/packages/$TARGET
+rm -rf "$WORKSPACE/services/fuse/build"
+fpm_build $WORKSPACE/services/fuse "${PYTHON2_PKG_PREFIX}-arvados-fuse" 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/services/fuse/arvados_fuse.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Keep FUSE driver"
+
+# The node manager
+cd $WORKSPACE/packages/$TARGET
+rm -rf "$WORKSPACE/services/nodemanager/build"
+fpm_build $WORKSPACE/services/nodemanager arvados-node-manager 'Curoverse, Inc.' 'python' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/services/nodemanager/arvados_node_manager.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados node manager"
+
+# The Docker image cleaner
+cd $WORKSPACE/packages/$TARGET
+rm -rf "$WORKSPACE/services/dockercleaner/build"
+fpm_build $WORKSPACE/services/dockercleaner arvados-docker-cleaner 'Curoverse, Inc.' 'python3' "$(awk '($1 == "Version:"){print $2}' $WORKSPACE/services/dockercleaner/arvados_docker_cleaner.egg-info/PKG-INFO)" "--url=https://arvados.org" "--description=The Arvados Docker image cleaner"
+
+# Forked libcloud
+LIBCLOUD_DIR=$(mktemp -d)
+(
+    cd $LIBCLOUD_DIR
+    git clone $DASHQ_UNLESS_DEBUG https://github.com/curoverse/libcloud.git .
+    git checkout apache-libcloud-$LIBCLOUD_PIN
+    # libcloud is absurdly noisy without -q, so force -q here
+    OLD_DASHQ_UNLESS_DEBUG=$DASHQ_UNLESS_DEBUG
+    DASHQ_UNLESS_DEBUG=-q
+    handle_python_package
+    DASHQ_UNLESS_DEBUG=$OLD_DASHQ_UNLESS_DEBUG
+)
+fpm_build $LIBCLOUD_DIR "$PYTHON2_PKG_PREFIX"-apache-libcloud
+rm -rf $LIBCLOUD_DIR
+
+# Python 2 dependencies
+declare -a PIP_DOWNLOAD_SWITCHES=(--no-deps)
+# Add --no-use-wheel if this pip knows it.
+pip wheel --help >/dev/null 2>&1
+case "$?" in
+    0) PIP_DOWNLOAD_SWITCHES+=(--no-use-wheel) ;;
+    2) ;;
+    *) echo "WARNING: `pip wheel` test returned unknown exit code $?" ;;
+esac
+
+for deppkg in "${PYTHON_BACKPORTS[@]}"; do
+    outname=$(echo "$deppkg" | sed -e 's/^python-//' -e 's/[<=>].*//' -e 's/_/-/g' -e "s/^/${PYTHON2_PKG_PREFIX}-/")
+    case "$deppkg" in
+        httplib2|google-api-python-client)
+            # Work around 0640 permissions on some package files.
+            # See #7591 and #7991.
+            pyfpm_workdir=$(mktemp --tmpdir -d pyfpm-XXXXXX) && (
+                set -e
+                cd "$pyfpm_workdir"
+                pip install "${PIP_DOWNLOAD_SWITCHES[@]}" --download . "$deppkg"
+                tar -xf "$deppkg"-*.tar*
+                cd "$deppkg"-*/
+                "python$PYTHON2_VERSION" setup.py $DASHQ_UNLESS_DEBUG egg_info build
+                chmod -R go+rX .
+                set +e
+                # --iteration 2 provides an upgrade for previously built
+                # buggy packages.
+                fpm_build . "$outname" "" python "" --iteration 2
+                # The upload step uses the package timestamp to determine
+                # whether it's new.  --no-clobber plays nice with that.
+                mv --no-clobber "$outname"*.$FORMAT "$WORKSPACE/packages/$TARGET"
+            )
+            if [ 0 != "$?" ]; then
+                echo "ERROR: $deppkg build process failed"
+                EXITCODE=1
+            fi
+            if [ -n "$pyfpm_workdir" ]; then
+                rm -rf "$pyfpm_workdir"
+            fi
+            ;;
+        *)
+            fpm_build "$deppkg" "$outname"
+            ;;
+    esac
+done
+
+# Python 3 dependencies
+for deppkg in "${PYTHON3_BACKPORTS[@]}"; do
+    outname=$(echo "$deppkg" | sed -e 's/^python-//' -e 's/[<=>].*//' -e 's/_/-/g' -e "s/^/${PYTHON3_PKG_PREFIX}-/")
+    # The empty string is the vendor argument: these aren't Curoverse software.
+    fpm_build "$deppkg" "$outname" "" python3
+done
+
+# Build the API server package
+handle_rails_package arvados-api-server "$WORKSPACE/services/api" \
+    "$WORKSPACE/agpl-3.0.txt" --url="https://arvados.org" \
+    --description="Arvados API server - Arvados is a free and open source platform for big data science." \
+    --license="GNU Affero General Public License, version 3.0"
+
+# Build the workbench server package
+(
+    set -e
+    cd "$WORKSPACE/apps/workbench"
+
+    # We need to bundle to be ready even when we build a package without vendor directory
+    # because asset compilation requires it.
+    bundle install --path vendor/bundle >"$STDOUT_IF_DEBUG"
+
+    # clear the tmp directory; the asset generation step will recreate tmp/cache/assets,
+    # and we want that in the package, so it's easier to not exclude the tmp directory
+    # from the package - empty it instead.
+    rm -rf tmp
+    mkdir tmp
+
+    # Set up application.yml and production.rb so that asset precompilation works
+    \cp config/application.yml.example config/application.yml -f
+    \cp config/environments/production.rb.example config/environments/production.rb -f
+    sed -i 's/secret_token: ~/secret_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/' config/application.yml
+
+    RAILS_ENV=production RAILS_GROUPS=assets bundle exec rake assets:precompile >/dev/null
+
+    # Remove generated configuration files so they don't go in the package.
+    rm config/application.yml config/environments/production.rb
+)
+
+if [[ "$?" != "0" ]]; then
+  echo "ERROR: Asset precompilation failed"
+  EXITCODE=1
+else
+  handle_rails_package arvados-workbench "$WORKSPACE/apps/workbench" \
+      "$WORKSPACE/agpl-3.0.txt" --url="https://arvados.org" \
+      --description="Arvados Workbench - Arvados is a free and open source platform for big data science." \
+      --license="GNU Affero General Public License, version 3.0"
+fi
+
+# clean up temporary GOPATH
+rm -rf "$GOPATH"
+
+exit $EXITCODE
diff --git a/build/run-library.sh b/build/run-library.sh
new file mode 100755 (executable)
index 0000000..c2e9b40
--- /dev/null
@@ -0,0 +1,348 @@
+#!/bin/bash
+
+# A library of functions shared by the various scripts in this directory.
+
+# This is the timestamp about when we merged changed to include licenses
+# with Arvados packages.  We use it as a heuristic to add revisions for
+# older packages.
+LICENSE_PACKAGE_TS=20151208015500
+
+debug_echo () {
+    echo "$@" >"$STDOUT_IF_DEBUG"
+}
+
+find_easy_install() {
+    for version_suffix in "$@"; do
+        if "easy_install$version_suffix" --version >/dev/null 2>&1; then
+            echo "easy_install$version_suffix"
+            return 0
+        fi
+    done
+    cat >&2 <<EOF
+$helpmessage
+
+Error: easy_install$1 (from Python setuptools module) not found
+
+EOF
+    exit 1
+}
+
+format_last_commit_here() {
+    local format="$1"; shift
+    TZ=UTC git log -n1 --first-parent "--format=format:$format" .
+}
+
+version_from_git() {
+  # Generates a version number from the git log for the current working
+  # directory, and writes it to stdout.
+  local git_ts git_hash
+  declare $(format_last_commit_here "git_ts=%ct git_hash=%h")
+  echo "0.1.$(date -ud "@$git_ts" +%Y%m%d%H%M%S).$git_hash"
+}
+
+nohash_version_from_git() {
+    version_from_git | cut -d. -f1-3
+}
+
+timestamp_from_git() {
+    format_last_commit_here "%ct"
+}
+
+handle_python_package () {
+  # This function assumes the current working directory is the python package directory
+  if [ -n "$(find dist -name "*-$(nohash_version_from_git).tar.gz" -print -quit)" ]; then
+    # This package doesn't need rebuilding.
+    return
+  fi
+  # Make sure only to use sdist - that's the only format pip can deal with (sigh)
+  python setup.py $DASHQ_UNLESS_DEBUG sdist
+}
+
+handle_ruby_gem() {
+    local gem_name="$1"; shift
+    local gem_version="$(nohash_version_from_git)"
+    local gem_src_dir="$(pwd)"
+
+    if ! [[ -e "${gem_name}-${gem_version}.gem" ]]; then
+        find -maxdepth 1 -name "${gem_name}-*.gem" -delete
+
+        # -q appears to be broken in gem version 2.2.2
+        $GEM build "$gem_name.gemspec" $DASHQ_UNLESS_DEBUG >"$STDOUT_IF_DEBUG" 2>"$STDERR_IF_DEBUG"
+    fi
+}
+
+# Usage: package_go_binary services/foo arvados-foo "Compute foo to arbitrary precision"
+package_go_binary() {
+    local src_path="$1"; shift
+    local prog="$1"; shift
+    local description="$1"; shift
+    local license_file="${1:-agpl-3.0.txt}"; shift
+
+    debug_echo "package_go_binary $src_path as $prog"
+
+    local basename="${src_path##*/}"
+
+    mkdir -p "$GOPATH/src/git.curoverse.com"
+    ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git"
+
+    cd "$GOPATH/src/git.curoverse.com/arvados.git/$src_path"
+    local version="$(version_from_git)"
+    local timestamp="$(timestamp_from_git)"
+
+    # If the command imports anything from the Arvados SDK, bump the
+    # version number and build a new package whenever the SDK changes.
+    if grep -qr git.curoverse.com/arvados .; then
+        cd "$GOPATH/src/git.curoverse.com/arvados.git/sdk/go"
+        if [[ $(timestamp_from_git) -gt "$timestamp" ]]; then
+            version=$(version_from_git)
+        fi
+    fi
+
+    cd $WORKSPACE/packages/$TARGET
+    go get "git.curoverse.com/arvados.git/$src_path"
+    fpm_build "$GOPATH/bin/$basename=/usr/bin/$prog" "$prog" 'Curoverse, Inc.' dir "$version" "--url=https://arvados.org" "--license=GNU Affero General Public License, version 3.0" "--description=$description" "$WORKSPACE/$license_file=/usr/share/doc/$prog/$license_file"
+}
+
+default_iteration() {
+    local package_name="$1"; shift
+    local package_version="$1"; shift
+    local iteration=1
+    if [[ $package_version =~ ^0\.1\.([0-9]{14})(\.|$) ]] && \
+           [[ ${BASH_REMATCH[1]} -le $LICENSE_PACKAGE_TS ]]; then
+        iteration=2
+    fi
+    echo $iteration
+}
+
+_build_rails_package_scripts() {
+    local pkgname="$1"; shift
+    local destdir="$1"; shift
+    local srcdir="$RUN_BUILD_PACKAGES_PATH/rails-package-scripts"
+    for scriptname in postinst prerm postrm; do
+        cat "$srcdir/$pkgname.sh" "$srcdir/step2.sh" "$srcdir/$scriptname.sh" \
+            >"$destdir/$scriptname" || return $?
+    done
+}
+
+handle_rails_package() {
+    local pkgname="$1"; shift
+    local srcdir="$1"; shift
+    local license_path="$1"; shift
+    local scripts_dir="$(mktemp --tmpdir -d "$pkgname-XXXXXXXX.scripts")" && \
+    local version_file="$(mktemp --tmpdir "$pkgname-XXXXXXXX.version")" && (
+        set -e
+        _build_rails_package_scripts "$pkgname" "$scripts_dir"
+        cd "$srcdir"
+        mkdir -p tmp
+        version_from_git >"$version_file"
+        git rev-parse HEAD >git-commit.version
+        bundle package --all
+    )
+    if [[ 0 != "$?" ]] || ! cd "$WORKSPACE/packages/$TARGET"; then
+        echo "ERROR: $pkgname package prep failed" >&2
+        rm -rf "$scripts_dir" "$version_file"
+        EXITCODE=1
+        return 1
+    fi
+    local railsdir="/var/www/${pkgname%-server}/current"
+    local -a pos_args=("$srcdir/=$railsdir" "$pkgname" "Curoverse, Inc." dir
+                       "$(cat "$version_file")")
+    local license_arg="$license_path=$railsdir/$(basename "$license_path")"
+    # --iteration=5 accommodates the package script bugfixes #8371 and #8413.
+    local -a switches=(--iteration=5
+                       --after-install "$scripts_dir/postinst"
+                       --before-remove "$scripts_dir/prerm"
+                       --after-remove "$scripts_dir/postrm")
+    # For some reason fpm excludes need to not start with /.
+    local exclude_root="${railsdir#/}"
+    # .git and packages are for the SSO server, which is built from its
+    # repository root.
+    local -a exclude_list=(.git packages tmp log coverage Capfile\* \
+                           config/deploy\* config/application.yml)
+    # for arvados-workbench, we need to have the (dummy) config/database.yml in the package
+    if  [[ "$pkgname" != "arvados-workbench" ]]; then
+      exclude_list+=('config/database.yml')
+    fi
+    for exclude in ${exclude_list[@]}; do
+        switches+=(-x "$exclude_root/$exclude")
+    done
+    fpm_build "${pos_args[@]}" "${switches[@]}" \
+              -x "$exclude_root/vendor/bundle" "$@" "$license_arg"
+    rm -rf "$scripts_dir" "$version_file"
+}
+
+# Build packages for everything
+fpm_build () {
+  # The package source.  Depending on the source type, this can be a
+  # path, or the name of the package in an upstream repository (e.g.,
+  # pip).
+  PACKAGE=$1
+  shift
+  # The name of the package to build.  Defaults to $PACKAGE.
+  PACKAGE_NAME=${1:-$PACKAGE}
+  shift
+  # Optional: the vendor of the package.  Should be "Curoverse, Inc." for
+  # packages of our own software.  Passed to fpm --vendor.
+  VENDOR=$1
+  shift
+  # The type of source package.  Passed to fpm -s.  Default "python".
+  PACKAGE_TYPE=${1:-python}
+  shift
+  # Optional: the package version number.  Passed to fpm -v.
+  VERSION=$1
+  shift
+
+  case "$PACKAGE_TYPE" in
+      python)
+          # All Arvados Python2 packages depend on Python 2.7.
+          # Make sure we build with that for consistency.
+          set -- "$@" --python-bin python2.7 \
+              --python-easyinstall "$EASY_INSTALL2" \
+              --python-package-name-prefix "$PYTHON2_PKG_PREFIX" \
+              --depends "$PYTHON2_PACKAGE"
+          ;;
+      python3)
+          # fpm does not actually support a python3 package type.  Instead
+          # we recognize it as a convenience shortcut to add several
+          # necessary arguments to fpm's command line later, after we're
+          # done handling positional arguments.
+          PACKAGE_TYPE=python
+          set -- "$@" --python-bin python3 \
+              --python-easyinstall "$EASY_INSTALL3" \
+              --python-package-name-prefix "$PYTHON3_PKG_PREFIX" \
+              --depends "$PYTHON3_PACKAGE"
+          ;;
+  esac
+
+  declare -a COMMAND_ARR=("fpm" "--maintainer=Ward Vandewege <ward@curoverse.com>" "-s" "$PACKAGE_TYPE" "-t" "$FORMAT")
+  if [ python = "$PACKAGE_TYPE" ]; then
+    COMMAND_ARR+=(--exclude=\*/{dist,site}-packages/tests/\*)
+    if [ deb = "$FORMAT" ]; then
+        # Dependencies are built from setup.py.  Since setup.py will never
+        # refer to Debian package iterations, it doesn't make sense to
+        # enforce those in the .deb dependencies.
+        COMMAND_ARR+=(--deb-ignore-iteration-in-dependencies)
+    fi
+  fi
+
+  if [[ "${DEBUG:-0}" != "0" ]]; then
+    COMMAND_ARR+=('--verbose' '--log' 'info')
+  fi
+
+  if [[ "$PACKAGE_NAME" != "$PACKAGE" ]]; then
+    COMMAND_ARR+=('-n' "$PACKAGE_NAME")
+  fi
+
+  if [[ "$VENDOR" != "" ]]; then
+    COMMAND_ARR+=('--vendor' "$VENDOR")
+  fi
+
+  if [[ "$VERSION" != "" ]]; then
+    COMMAND_ARR+=('-v' "$VERSION")
+  fi
+  # We can always add an --iteration here.  If another one is specified in $@,
+  # that will take precedence, as desired.
+  COMMAND_ARR+=(--iteration "$(default_iteration "$PACKAGE" "$VERSION")")
+
+  # 'dir' type packages are provided in the form /path/to/source=/path/to/dest
+  # so strip off the 2nd part to check for fpm-info below.
+  PACKAGE_DIR=$(echo $PACKAGE | sed 's/\/=.*//')
+
+  # Append --depends X and other arguments specified by fpm-info.sh in
+  # the package source dir. These are added last so they can override
+  # the arguments added by this script.
+  declare -a fpm_args=()
+  declare -a build_depends=()
+  declare -a fpm_depends=()
+  declare -a fpm_exclude=()
+  FPM_INFO=""
+  if [[ -d "$PACKAGE_DIR" ]]; then
+      FPM_INFO="$PACKAGE_DIR/fpm-info.sh"
+  elif [[ -e "${WORKSPACE}/backports/${PACKAGE_TYPE}-${PACKAGE}/fpm-info.sh" ]]; then
+      FPM_INFO="${WORKSPACE}/backports/${PACKAGE_TYPE}-${PACKAGE}/fpm-info.sh"
+      debug_echo "Found fpm-info.sh in backports: $FPM_INFO"
+  elif [[ -e "${WORKSPACE}/backports/${PACKAGE_TYPE}-${PACKAGE_NAME}/fpm-info.sh" ]]; then
+      FPM_INFO="${WORKSPACE}/backports/${PACKAGE_TYPE}-${PACKAGE_NAME}/fpm-info.sh"
+  fi
+  if [[ -e "$FPM_INFO" ]]; then
+      debug_echo "Loading fpm overrides from $FPM_INFO"
+      source "$FPM_INFO"
+  fi
+  for pkg in "${build_depends[@]}"; do
+      if [[ $TARGET =~ debian|ubuntu ]]; then
+          pkg_deb=$(ls "$WORKSPACE/packages/$TARGET/$pkg_"*.deb | sort -rg | awk 'NR==1')
+          if [[ -e $pkg_deb ]]; then
+              echo "Installing build_dep $pkg from $pkg_deb"
+              dpkg -i "$pkg_deb"
+          else
+              echo "Attemping to install build_dep $pkg using apt-get"
+              apt-get install -y "$pkg"
+          fi
+          apt-get -y -f install
+      else
+          pkg_rpm=$(ls "$WORKSPACE/packages/$TARGET/$pkg"-[0-9]*.rpm | sort -rg | awk 'NR==1')
+          if [[ -e $pkg_rpm ]]; then
+              echo "Installing build_dep $pkg from $pkg_rpm"
+              rpm -i "$pkg_rpm"
+          else
+              echo "Attemping to install build_dep $pkg"
+              rpm -i "$pkg"
+          fi
+      fi
+  done
+  for i in "${fpm_depends[@]}"; do
+    COMMAND_ARR+=('--depends' "$i")
+  done
+  for i in "${fpm_exclude[@]}"; do
+    COMMAND_ARR+=('--exclude' "$i")
+  done
+
+  # Append remaining function arguments directly to fpm's command line.
+  for i; do
+    COMMAND_ARR+=("$i")
+  done
+
+  COMMAND_ARR+=("${fpm_args[@]}")
+
+  COMMAND_ARR+=("$PACKAGE")
+
+  debug_echo -e "\n${COMMAND_ARR[@]}\n"
+
+  FPM_RESULTS=$("${COMMAND_ARR[@]}")
+  FPM_EXIT_CODE=$?
+
+  fpm_verify $FPM_EXIT_CODE $FPM_RESULTS
+}
+
+# verify build results
+fpm_verify () {
+  FPM_EXIT_CODE=$1
+  shift
+  FPM_RESULTS=$@
+
+  FPM_PACKAGE_NAME=''
+  if [[ $FPM_RESULTS =~ ([A-Za-z0-9_\.-]*\.)(deb|rpm) ]]; then
+    FPM_PACKAGE_NAME=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
+  fi
+
+  if [[ "$FPM_PACKAGE_NAME" == "" ]]; then
+    EXITCODE=1
+    echo "Error: $PACKAGE: Unable to figure out package name from fpm results:"
+    echo
+    echo $FPM_RESULTS
+    echo
+  elif [[ "$FPM_RESULTS" =~ "File already exists" ]]; then
+    echo "Package $FPM_PACKAGE_NAME exists, not rebuilding"
+  elif [[ 0 -ne "$FPM_EXIT_CODE" ]]; then
+    echo "Error building package for $1:\n $FPM_RESULTS"
+  fi
+}
+
+install_package() {
+  PACKAGES=$@
+  if [[ "$FORMAT" == "deb" ]]; then
+    $SUDO apt-get install $PACKAGES --yes
+  elif [[ "$FORMAT" == "rpm" ]]; then
+    $SUDO yum -q -y install $PACKAGES
+  fi
+}
diff --git a/build/run-tests.sh b/build/run-tests.sh
new file mode 100755 (executable)
index 0000000..1552e40
--- /dev/null
@@ -0,0 +1,842 @@
+#!/bin/bash
+
+. `dirname "$(readlink -f "$0")"`/libcloud-pin
+
+read -rd "\000" helpmessage <<EOF
+$(basename $0): Install and test Arvados components.
+
+Exit non-zero if any tests fail.
+
+Syntax:
+        $(basename $0) WORKSPACE=/path/to/arvados [options]
+
+Options:
+
+--skip FOO     Do not test the FOO component.
+--only FOO     Do not test anything except the FOO component.
+--temp DIR     Install components and dependencies under DIR instead of
+               making a new temporary directory. Implies --leave-temp.
+--leave-temp   Do not remove GOPATH, virtualenv, and other temp dirs at exit.
+               Instead, show the path to give as --temp to reuse them in
+               subsequent invocations.
+--skip-install Do not run any install steps. Just run tests.
+               You should provide GOPATH, GEMHOME, and VENVDIR options
+               from a previous invocation if you use this option.
+--only-install Run specific install step
+WORKSPACE=path Arvados source tree to test.
+CONFIGSRC=path Dir with api server config files to copy into source tree.
+               (If none given, leave config files alone in source tree.)
+services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.rb"
+               Restrict apiserver tests to the given file
+sdk/python_test="--test-suite test.test_keep_locator"
+               Restrict Python SDK tests to the given class
+apps/workbench_test="TEST=test/integration/pipeline_instances_test.rb"
+               Restrict Workbench tests to the given file
+services/arv-git-httpd_test="-check.vv"
+               Show all log messages, even when tests pass (also works
+               with services/keepstore_test etc.)
+ARVADOS_DEBUG=1
+               Print more debug messages
+envvar=value   Set \$envvar to value. Primarily useful for WORKSPACE,
+               *_test, and other examples shown above.
+
+Assuming --skip-install is not given, all components are installed
+into \$GOPATH, \$VENDIR, and \$GEMHOME before running any tests. Many
+test suites depend on other components being installed, and installing
+everything tends to be quicker than debugging dependencies.
+
+As a special concession to the current CI server config, CONFIGSRC
+defaults to $HOME/arvados-api-server if that directory exists.
+
+More information and background:
+
+https://arvados.org/projects/arvados/wiki/Running_tests
+
+Available tests:
+
+apps/workbench
+apps/workbench_benchmark
+apps/workbench_profile
+doc
+services/api
+services/arv-git-httpd
+services/crunchstat
+services/dockercleaner
+services/fuse
+services/keep-web
+services/keepproxy
+services/keepstore
+services/login-sync
+services/nodemanager
+services/crunch-dispatch-local
+sdk/cli
+sdk/pam
+sdk/python
+sdk/ruby
+sdk/go/arvadosclient
+sdk/go/keepclient
+sdk/go/manifest
+sdk/go/blockdigest
+sdk/go/streamer
+sdk/go/crunchrunner
+sdk/cwl
+tools/crunchstat-summary
+tools/keep-rsync
+
+EOF
+
+# First make sure to remove any ARVADOS_ variables from the calling
+# environment that could interfere with the tests.
+unset $(env | cut -d= -f1 | grep \^ARVADOS_)
+
+# Reset other variables that could affect our [tests'] behavior by
+# accident.
+GITDIR=
+GOPATH=
+VENVDIR=
+VENV3DIR=
+PYTHONPATH=
+GEMHOME=
+PERLINSTALLBASE=
+
+COLUMNS=80
+
+skip_install=
+temp=
+temp_preserve=
+
+clear_temp() {
+    if [[ -z "$temp" ]]; then
+        # we didn't even get as far as making a temp dir
+        :
+    elif [[ -z "$temp_preserve" ]]; then
+        rm -rf "$temp"
+    else
+        echo "Leaving behind temp dirs in $temp"
+    fi
+}
+
+fatal() {
+    clear_temp
+    echo >&2 "Fatal: $* (encountered in ${FUNCNAME[1]} at ${BASH_SOURCE[1]} line ${BASH_LINENO[0]})"
+    exit 1
+}
+
+report_outcomes() {
+    for x in "${successes[@]}"
+    do
+        echo "Pass: $x"
+    done
+
+    if [[ ${#failures[@]} == 0 ]]
+    then
+        echo "All test suites passed."
+    else
+        echo "Failures (${#failures[@]}):"
+        for x in "${failures[@]}"
+        do
+            echo "Fail: $x"
+        done
+    fi
+}
+
+exit_cleanly() {
+    trap - INT
+    create-plot-data-from-log.sh $BUILD_NUMBER "$WORKSPACE/apps/workbench/log/test.log" "$WORKSPACE/apps/workbench/log/"
+    rotate_logfile "$WORKSPACE/apps/workbench/log/" "test.log"
+    stop_services
+    rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
+    report_outcomes
+    clear_temp
+    exit ${#failures}
+}
+
+sanity_checks() {
+    ( [[ -n "$WORKSPACE" ]] && [[ -d "$WORKSPACE/services" ]] ) \
+        || fatal "WORKSPACE environment variable not set to a source directory (see: $0 --help)"
+    echo Checking dependencies:
+    echo -n 'virtualenv: '
+    virtualenv --version \
+        || fatal "No virtualenv. Try: apt-get install virtualenv (on ubuntu: python-virtualenv)"
+    echo -n 'go: '
+    go version \
+        || fatal "No go binary. See http://golang.org/doc/install"
+    echo -n 'gcc: '
+    gcc --version | egrep ^gcc \
+        || fatal "No gcc. Try: apt-get install build-essential"
+    echo -n 'fuse.h: '
+    find /usr/include -wholename '*fuse/fuse.h' \
+        || fatal "No fuse/fuse.h. Try: apt-get install libfuse-dev"
+    echo -n 'pyconfig.h: '
+    find /usr/include -name pyconfig.h | egrep --max-count=1 . \
+        || fatal "No pyconfig.h. Try: apt-get install python-dev"
+    echo -n 'nginx: '
+    PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" nginx -v \
+        || fatal "No nginx. Try: apt-get install nginx"
+    echo -n 'perl: '
+    perl -v | grep version \
+        || fatal "No perl. Try: apt-get install perl"
+    for mod in ExtUtils::MakeMaker JSON LWP Net::SSL; do
+        echo -n "perl $mod: "
+        perl -e "use $mod; print \"\$$mod::VERSION\\n\"" \
+            || fatal "No $mod. Try: apt-get install perl-modules libcrypt-ssleay-perl libjson-perl libwww-perl"
+    done
+    echo -n 'gitolite: '
+    which gitolite \
+        || fatal "No gitolite. Try: apt-get install gitolite3"
+}
+
+rotate_logfile() {
+  # i.e.  rotate_logfile "$WORKSPACE/apps/workbench/log/" "test.log"
+  # $BUILD_NUMBER is set by Jenkins if this script is being called as part of a Jenkins run
+  if [[ -f "$1/$2" ]]; then
+    THEDATE=`date +%Y%m%d%H%M%S`
+    mv "$1/$2" "$1/$THEDATE-$BUILD_NUMBER-$2"
+    gzip "$1/$THEDATE-$BUILD_NUMBER-$2"
+  fi
+}
+
+declare -a failures
+declare -A skip
+declare -A testargs
+skip[apps/workbench_profile]=1
+
+while [[ -n "$1" ]]
+do
+    arg="$1"; shift
+    case "$arg" in
+        --help)
+            echo >&2 "$helpmessage"
+            echo >&2
+            exit 1
+            ;;
+        --skip)
+            skipwhat="$1"; shift
+            skip[$skipwhat]=1
+            ;;
+        --only)
+            only="$1"; skip[$1]=""; shift
+            ;;
+        --skip-install)
+            skip_install=1
+            ;;
+        --only-install)
+            skip_install=1
+            only_install="$1"; shift
+            ;;
+        --temp)
+            temp="$1"; shift
+            temp_preserve=1
+            ;;
+        --leave-temp)
+            temp_preserve=1
+            ;;
+        --retry)
+            retry=1
+            ;;
+        *_test=*)
+            suite="${arg%%_test=*}"
+            args="${arg#*=}"
+            testargs["$suite"]="$args"
+            ;;
+        *=*)
+            eval export $(echo $arg | cut -d= -f1)=\"$(echo $arg | cut -d= -f2-)\"
+            ;;
+        *)
+            echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
+            exit 1
+            ;;
+    esac
+done
+
+start_api() {
+    echo 'Starting API server...'
+    cd "$WORKSPACE" \
+        && eval $(python sdk/python/tests/run_test_server.py start --auth admin) \
+        && export ARVADOS_TEST_API_HOST="$ARVADOS_API_HOST" \
+        && export ARVADOS_TEST_API_INSTALLED="$$" \
+        && (env | egrep ^ARVADOS)
+}
+
+start_nginx_proxy_services() {
+    echo 'Starting keepproxy, keep-web, arv-git-httpd, and nginx ssl proxy...'
+    cd "$WORKSPACE" \
+        && python sdk/python/tests/run_test_server.py start_keep_proxy \
+        && python sdk/python/tests/run_test_server.py start_keep-web \
+        && python sdk/python/tests/run_test_server.py start_arv-git-httpd \
+        && python sdk/python/tests/run_test_server.py start_nginx \
+        && export ARVADOS_TEST_PROXY_SERVICES=1
+}
+
+stop_services() {
+    if [[ -n "$ARVADOS_TEST_PROXY_SERVICES" ]]; then
+        unset ARVADOS_TEST_PROXY_SERVICES
+        cd "$WORKSPACE" \
+            && python sdk/python/tests/run_test_server.py stop_nginx \
+            && python sdk/python/tests/run_test_server.py stop_arv-git-httpd \
+            && python sdk/python/tests/run_test_server.py stop_keep-web \
+            && python sdk/python/tests/run_test_server.py stop_keep_proxy
+    fi
+    if [[ -n "$ARVADOS_TEST_API_HOST" ]]; then
+        unset ARVADOS_TEST_API_HOST
+        cd "$WORKSPACE" \
+            && python sdk/python/tests/run_test_server.py stop
+    fi
+}
+
+interrupt() {
+    failures+=("($(basename $0) interrupted)")
+    exit_cleanly
+}
+trap interrupt INT
+
+sanity_checks
+
+echo "WORKSPACE=$WORKSPACE"
+
+if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
+    # Jenkins expects us to use this by default.
+    CONFIGSRC="$HOME/arvados-api-server"
+fi
+
+# Clean up .pyc files that may exist in the workspace
+cd "$WORKSPACE"
+find -name '*.pyc' -delete
+
+if [[ -z "$temp" ]]; then
+    temp="$(mktemp -d)"
+fi
+
+# Set up temporary install dirs (unless existing dirs were supplied)
+for tmpdir in VENVDIR VENV3DIR GOPATH GEMHOME PERLINSTALLBASE
+do
+    if [[ -z "${!tmpdir}" ]]; then
+        eval "$tmpdir"="$temp/$tmpdir"
+    fi
+    if ! [[ -d "${!tmpdir}" ]]; then
+        mkdir "${!tmpdir}" || fatal "can't create ${!tmpdir} (does $temp exist?)"
+    fi
+done
+
+setup_ruby_environment() {
+    if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
+      source "$HOME/.rvm/scripts/rvm"
+      using_rvm=true
+    elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
+      source "/usr/local/rvm/scripts/rvm"
+      using_rvm=true
+    else
+      using_rvm=false
+    fi
+
+    if [[ "$using_rvm" == true ]]; then
+        # If rvm is in use, we can't just put separate "dependencies"
+        # and "gems-under-test" paths to GEM_PATH: passenger resets
+        # the environment to the "current gemset", which would lose
+        # our GEM_PATH and prevent our test suites from running ruby
+        # programs (for example, the Workbench test suite could not
+        # boot an API server or run arv). Instead, we have to make an
+        # rvm gemset and use it for everything.
+
+        [[ `type rvm | head -n1` == "rvm is a function" ]] \
+            || fatal 'rvm check'
+
+        # Put rvm's favorite path back in first place (overriding
+        # virtualenv, which just put itself there). Ignore rvm's
+        # complaint about not being in first place already.
+        rvm use @default 2>/dev/null
+
+        # Create (if needed) and switch to an @arvados-tests
+        # gemset. (Leave the choice of ruby to the caller.)
+        rvm use @arvados-tests --create \
+            || fatal 'rvm gemset setup'
+
+        rvm env
+    else
+        # When our "bundle install"s need to install new gems to
+        # satisfy dependencies, we want them to go where "gem install
+        # --user-install" would put them. (However, if the caller has
+        # already set GEM_HOME, we assume that's where dependencies
+        # should be installed, and we should leave it alone.)
+
+        if [ -z "$GEM_HOME" ]; then
+            user_gempath="$(gem env gempath)"
+            export GEM_HOME="${user_gempath%%:*}"
+        fi
+        PATH="$(gem env gemdir)/bin:$PATH"
+
+        # When we build and install our own gems, we install them in our
+        # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
+        # PATH so integration tests prefer them over other versions that
+        # happen to be installed in $user_gempath, system dirs, etc.
+
+        tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
+        PATH="$tmpdir_gem_home/bin:$PATH"
+        export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
+
+        echo "Will install dependencies to $(gem env gemdir)"
+        echo "Will install arvados gems to $tmpdir_gem_home"
+        echo "Gem search path is GEM_PATH=$GEM_PATH"
+    fi
+}
+
+with_test_gemset() {
+    if [[ "$using_rvm" == true ]]; then
+        "$@"
+    else
+        GEM_HOME="$tmpdir_gem_home" GEM_PATH="$tmpdir_gem_home" "$@"
+    fi
+}
+
+gem_uninstall_if_exists() {
+    if gem list "$1\$" | egrep '^\w'; then
+        gem uninstall --force --all --executables "$1"
+    fi
+}
+
+setup_virtualenv() {
+    local venvdest="$1"; shift
+    if ! [[ -e "$venvdest/bin/activate" ]] || ! [[ -e "$venvdest/bin/pip" ]]; then
+        virtualenv --setuptools "$@" "$venvdest" || fatal "virtualenv $venvdest failed"
+    fi
+    "$venvdest/bin/pip" install 'setuptools>=18' 'pip>=7'
+    # ubuntu1404 can't seem to install mock via tests_require, but it can do this.
+    "$venvdest/bin/pip" install 'mock>=1.0' 'pbr<1.7.0'
+}
+
+export PERLINSTALLBASE
+export PERLLIB="$PERLINSTALLBASE/lib/perl5:${PERLLIB:+$PERLLIB}"
+
+export GOPATH
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
+    || fatal "symlink failed"
+
+setup_virtualenv "$VENVDIR" --python python2.7
+. "$VENVDIR/bin/activate"
+
+# Needed for run_test_server.py which is used by certain (non-Python) tests.
+pip freeze 2>/dev/null | egrep ^PyYAML= \
+    || pip install PyYAML >/dev/null \
+    || fatal "pip install PyYAML failed"
+
+# Preinstall forked version of libcloud, because nodemanager "pip install"
+# won't pick it up by default.
+pip freeze 2>/dev/null | egrep ^apache-libcloud==$LIBCLOUD_PIN \
+    || pip install --pre --ignore-installed https://github.com/curoverse/libcloud/archive/apache-libcloud-$LIBCLOUD_PIN.zip >/dev/null \
+    || fatal "pip install apache-libcloud failed"
+
+# This will help people who reuse --temp dirs when we upgrade to llfuse 0.42
+if egrep -q 'llfuse.*>= *0\.42' "$WORKSPACE/services/fuse/setup.py"; then
+    # Uninstall old llfuse, because services/fuse "pip install" won't
+    # upgrade it by default.
+    if pip freeze | egrep '^llfuse==0\.41\.'; then
+        yes | pip uninstall 'llfuse<0.42'
+    fi
+fi
+
+# Deactivate Python 2 virtualenv
+deactivate
+
+# If Python 3 is available, set up its virtualenv in $VENV3DIR.
+# Otherwise, skip dependent tests.
+PYTHON3=$(which python3)
+if [ "0" = "$?" ]; then
+    setup_virtualenv "$VENV3DIR" --python python3
+else
+    PYTHON3=
+    skip[services/dockercleaner]=1
+    cat >&2 <<EOF
+
+Warning: python3 could not be found
+services/dockercleaner install and tests will be skipped
+
+EOF
+fi
+
+# Reactivate Python 2 virtualenv
+. "$VENVDIR/bin/activate"
+
+# Note: this must be the last time we change PATH, otherwise rvm will
+# whine a lot.
+setup_ruby_environment
+
+echo "PATH is $PATH"
+
+if ! which bundler >/dev/null
+then
+    gem install --user-install bundler || fatal 'Could not install bundler'
+fi
+
+checkexit() {
+    if [[ "$1" != "0" ]]; then
+        title "!!!!!! $2 FAILED !!!!!!"
+        failures+=("$2 (`timer`)")
+    else
+        successes+=("$2 (`timer`)")
+    fi
+}
+
+timer_reset() {
+    t0=$SECONDS
+}
+
+timer() {
+    echo -n "$(($SECONDS - $t0))s"
+}
+
+retry() {
+    while ! ${@} && [[ "$retry" == 1 ]]
+    do
+        read -p 'Try again? [Y/n] ' x
+        if [[ "$x" != "y" ]] && [[ "$x" != "" ]]
+        then
+            break
+        fi
+    done
+}
+
+do_test() {
+    retry do_test_once ${@}
+}
+
+do_test_once() {
+    unset result
+    if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
+    then
+        title "Running $1 tests"
+        timer_reset
+        if [[ "$2" == "go" ]]
+        then
+            covername="coverage-$(echo "$1" | sed -e 's/\//_/g')"
+            coverflags=("-covermode=count" "-coverprofile=$WORKSPACE/tmp/.$covername.tmp")
+            # We do "go get -t" here to catch compilation errors
+            # before trying "go test". Otherwise, coverage-reporting
+            # mode makes Go show the wrong line numbers when reporting
+            # compilation errors.
+            if [[ -n "${testargs[$1]}" ]]
+            then
+                # "go test -check.vv giturl" doesn't work, but this
+                # does:
+                cd "$WORKSPACE/$1" && \
+                    go get -t "git.curoverse.com/arvados.git/$1" && \
+                    go test ${coverflags[@]} ${testargs[$1]}
+            else
+                # The above form gets verbose even when testargs is
+                # empty, so use this form in such cases:
+                go get -t "git.curoverse.com/arvados.git/$1" && \
+                    go test ${coverflags[@]} "git.curoverse.com/arvados.git/$1"
+            fi
+            result="$?"
+            go tool cover -html="$WORKSPACE/tmp/.$covername.tmp" -o "$WORKSPACE/tmp/$covername.html"
+            rm "$WORKSPACE/tmp/.$covername.tmp"
+        elif [[ "$2" == "pip" ]]
+        then
+            # $3 can name a path directory for us to use, including trailing
+            # slash; e.g., the bin/ subdirectory of a virtualenv.
+            cd "$WORKSPACE/$1" \
+                && "${3}python" setup.py test ${testargs[$1]}
+        elif [[ "$2" != "" ]]
+        then
+            "test_$2"
+        else
+            "test_$1"
+        fi
+        result=${result:-$?}
+        checkexit $result "$1 tests"
+        title "End of $1 tests (`timer`)"
+        return $result
+    else
+        title "Skipping $1 tests"
+    fi
+}
+
+do_install() {
+    retry do_install_once ${@}
+}
+
+do_install_once() {
+    if [[ -z "$skip_install" || (-n "$only_install" && "$only_install" == "$1") ]]
+    then
+        title "Running $1 install"
+        timer_reset
+        if [[ "$2" == "go" ]]
+        then
+            go get -t "git.curoverse.com/arvados.git/$1"
+        elif [[ "$2" == "pip" ]]
+        then
+            # $3 can name a path directory for us to use, including trailing
+            # slash; e.g., the bin/ subdirectory of a virtualenv.
+
+            # Need to change to a different directory after creating
+            # the source dist package to avoid a pip bug.
+            # see https://arvados.org/issues/5766 for details.
+
+            # Also need to install twice, because if it believes the package is
+            # already installed, pip it won't install it.  So the first "pip
+            # install" ensures that the dependencies are met, the second "pip
+            # install" ensures that we've actually installed the local package
+            # we just built.
+            cd "$WORKSPACE/$1" \
+                && "${3}python" setup.py sdist rotate --keep=1 --match .tar.gz \
+                && cd "$WORKSPACE" \
+                && "${3}pip" install --quiet "$WORKSPACE/$1/dist"/*.tar.gz \
+                && "${3}pip" install --quiet --no-deps --ignore-installed "$WORKSPACE/$1/dist"/*.tar.gz
+        elif [[ "$2" != "" ]]
+        then
+            "install_$2"
+        else
+            "install_$1"
+        fi
+        result=$?
+        checkexit $result "$1 install"
+        title "End of $1 install (`timer`)"
+        return $result
+    else
+        title "Skipping $1 install"
+    fi
+}
+
+title () {
+    txt="********** $1 **********"
+    printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
+}
+
+bundle_install_trylocal() {
+    (
+        set -e
+        echo "(Running bundle install --local. 'could not find package' messages are OK.)"
+        if ! bundle install --local --no-deployment; then
+            echo "(Running bundle install again, without --local.)"
+            bundle install --no-deployment
+        fi
+        bundle package --all
+    )
+}
+
+install_doc() {
+    cd "$WORKSPACE/doc" \
+        && bundle_install_trylocal \
+        && rm -rf .site
+}
+do_install doc
+
+install_gem() {
+    gemname=$1
+    srcpath=$2
+    with_test_gemset gem_uninstall_if_exists "$gemname" \
+        && cd "$WORKSPACE/$srcpath" \
+        && bundle_install_trylocal \
+        && gem build "$gemname.gemspec" \
+        && with_test_gemset gem install --no-ri --no-rdoc $(ls -t "$gemname"-*.gem|head -n1)
+}
+
+install_ruby_sdk() {
+    install_gem arvados sdk/ruby
+}
+do_install sdk/ruby ruby_sdk
+
+install_perl_sdk() {
+    cd "$WORKSPACE/sdk/perl" \
+        && perl Makefile.PL INSTALL_BASE="$PERLINSTALLBASE" \
+        && make install INSTALLDIRS=perl
+}
+do_install sdk/perl perl_sdk
+
+install_cli() {
+    install_gem arvados-cli sdk/cli
+}
+do_install sdk/cli cli
+
+install_login-sync() {
+    install_gem arvados-login-sync services/login-sync
+}
+do_install services/login-sync login-sync
+
+# Install the Python SDK early. Various other test suites (like
+# keepproxy) bring up run_test_server.py, which imports the arvados
+# module. We can't actually *test* the Python SDK yet though, because
+# its own test suite brings up some of those other programs (like
+# keepproxy).
+declare -a pythonstuff
+pythonstuff=(
+    sdk/pam
+    sdk/python
+    sdk/cwl
+    services/fuse
+    services/nodemanager
+    tools/crunchstat-summary
+    )
+for p in "${pythonstuff[@]}"
+do
+    do_install "$p" pip
+done
+if [ -n "$PYTHON3" ]; then
+    do_install services/dockercleaner pip "$VENV3DIR/bin/"
+fi
+
+install_apiserver() {
+    cd "$WORKSPACE/services/api" \
+        && RAILS_ENV=test bundle_install_trylocal
+
+    rm -f config/environments/test.rb
+    cp config/environments/test.rb.example config/environments/test.rb
+
+    if [ -n "$CONFIGSRC" ]
+    then
+        for f in database.yml application.yml
+        do
+            cp "$CONFIGSRC/$f" config/ || fatal "$f"
+        done
+    fi
+
+    # Fill in a random secret_token and blob_signing_key for testing
+    SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
+    BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
+
+    sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
+    sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
+
+    # Set up empty git repo (for git tests)
+    GITDIR=$(mktemp -d)
+    sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
+
+    rm -rf $GITDIR
+    mkdir -p $GITDIR/test
+    cd $GITDIR/test \
+        && git init \
+        && git config user.email "jenkins@ci.curoverse.com" \
+        && git config user.name "Jenkins, CI" \
+        && touch tmp \
+        && git add tmp \
+        && git commit -m 'initial commit'
+
+    # Clear out any lingering postgresql connections to the test
+    # database, so that we can drop it. This assumes the current user
+    # is a postgresql superuser.
+    cd "$WORKSPACE/services/api" \
+        && test_database=$(python -c "import yaml; print yaml.load(file('config/database.yml'))['test']['database']") \
+        && psql "$test_database" -c "SELECT pg_terminate_backend (pg_stat_activity.procpid::int) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$test_database';" 2>/dev/null
+
+    cd "$WORKSPACE/services/api" \
+        && RAILS_ENV=test bundle exec rake db:drop \
+        && RAILS_ENV=test bundle exec rake db:setup \
+        && RAILS_ENV=test bundle exec rake db:fixtures:load
+}
+do_install services/api apiserver
+
+declare -a gostuff
+gostuff=(
+    sdk/go/arvadosclient
+    sdk/go/blockdigest
+    sdk/go/manifest
+    sdk/go/streamer
+    sdk/go/crunchrunner
+    services/arv-git-httpd
+    services/crunchstat
+    services/keep-web
+    services/keepstore
+    sdk/go/keepclient
+    services/keepproxy
+    services/datamanager/summary
+    services/datamanager/collection
+    services/datamanager/keep
+    services/datamanager
+    services/crunch-dispatch-local
+    services/crunch-run
+    tools/keep-rsync
+    )
+for g in "${gostuff[@]}"
+do
+    do_install "$g" go
+done
+
+install_workbench() {
+    cd "$WORKSPACE/apps/workbench" \
+        && mkdir -p tmp/cache \
+        && RAILS_ENV=test bundle_install_trylocal
+}
+do_install apps/workbench workbench
+
+test_doclinkchecker() {
+    (
+        set -e
+        cd "$WORKSPACE/doc"
+        ARVADOS_API_HOST=qr1hi.arvadosapi.com
+        # Make sure python-epydoc is installed or the next line won't
+        # do much good!
+        PYTHONPATH=$WORKSPACE/sdk/python/ bundle exec rake linkchecker baseurl=file://$WORKSPACE/doc/.site/ arvados_workbench_host=https://workbench.$ARVADOS_API_HOST arvados_api_host=$ARVADOS_API_HOST
+    )
+}
+do_test doc doclinkchecker
+
+stop_services
+
+test_apiserver() {
+    rm -f "$WORKSPACE/services/api/git-commit.version"
+    cd "$WORKSPACE/services/api" \
+        && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[services/api]}
+}
+do_test services/api apiserver
+
+# Shortcut for when we're only running apiserver tests. This saves a bit of time,
+# because we don't need to start up the api server for subsequent tests.
+if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
+  rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
+  exit_cleanly
+fi
+
+start_api
+
+test_ruby_sdk() {
+    cd "$WORKSPACE/sdk/ruby" \
+        && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
+}
+do_test sdk/ruby ruby_sdk
+
+test_cli() {
+    cd "$WORKSPACE/sdk/cli" \
+        && mkdir -p /tmp/keep \
+        && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
+}
+do_test sdk/cli cli
+
+test_login-sync() {
+    cd "$WORKSPACE/services/login-sync" \
+        && bundle exec rake test TESTOPTS=-v ${testargs[services/login-sync]}
+}
+do_test services/login-sync login-sync
+
+for p in "${pythonstuff[@]}"
+do
+    do_test "$p" pip
+done
+do_test services/dockercleaner pip "$VENV3DIR/bin/"
+
+for g in "${gostuff[@]}"
+do
+    do_test "$g" go
+done
+
+test_workbench() {
+    start_nginx_proxy_services \
+        && cd "$WORKSPACE/apps/workbench" \
+        && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[apps/workbench]}
+}
+do_test apps/workbench workbench
+
+test_workbench_benchmark() {
+    start_nginx_proxy_services \
+        && cd "$WORKSPACE/apps/workbench" \
+        && RAILS_ENV=test bundle exec rake test:benchmark ${testargs[apps/workbench_benchmark]}
+}
+do_test apps/workbench_benchmark workbench_benchmark
+
+test_workbench_profile() {
+    start_nginx_proxy_services \
+        && cd "$WORKSPACE/apps/workbench" \
+        && RAILS_ENV=test bundle exec rake test:profile ${testargs[apps/workbench_profile]}
+}
+do_test apps/workbench_profile workbench_profile
+
+exit_cleanly
diff --git a/crunch_scripts/crunchrunner b/crunch_scripts/crunchrunner
new file mode 100755 (executable)
index 0000000..71c10c9
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec $TASK_KEEPMOUNT/$JOB_PARAMETER_CRUNCHRUNNER
index 05b7437d750286c9e6c8978f080467e084c527de..2ee1f5dcf39fbb381cde41fa273fd6c4294a525f 100644 (file)
@@ -144,6 +144,8 @@ navbar:
   installguide:
     - Overview:
       - install/index.html.textile.liquid
+    - Docker quick start:
+      - install/arvbox.html.textile.liquid
     - Manual installation:
       - install/install-manual-prerequisites.html.textile.liquid
       - install/install-sso.html.textile.liquid
index 3fb754f9db139d4a659f5aba0dec9487f1fe488c..20fe6c2c2fd15e69522c9ebd4d5c2ad1c784d1ab 100644 (file)
@@ -27,7 +27,7 @@
                     "required": true,
                     "dataclass": "Collection"
                 },
-                "sample_subdir": "$(dir $(samples))",
+                "sample_subdir": "$(dir $(sample))",
                 "read_pair": {
                     "value": {
                         "group": "sample_subdir",
diff --git a/doc/install/arvbox.html.textile.liquid b/doc/install/arvbox.html.textile.liquid
new file mode 100644 (file)
index 0000000..3ddc7c8
--- /dev/null
@@ -0,0 +1,141 @@
+---
+layout: default
+navsection: installguide
+title: Arvados-in-a-box
+...
+
+Arvbox is a Docker-based self-contained development, demonstration and testing environment for Arvados.  It is not intended for production use.
+
+h2. Quick start
+
+<pre>
+$ git clone https://github.com/curoverse/arvados.git
+$ cd arvados/tools/arvbox/bin
+$ ./arvbox start localdemo
+</pre>
+
+h2. Requirements
+
+* Linux 3.x+ and Docker 1.9+
+* Minimum of 3 GiB of RAM  + additional memory to run jobs
+* Minimum of 3 GiB of disk + storage for actual data
+
+h2. Usage
+
+<pre>
+$ arvbox
+Arvados-in-a-box                      http://arvados.org
+
+arvbox (build|start|run|open|shell|ip|stop|rebuild|reset|destroy|log|svrestart)
+
+build <config>      build arvbox Docker image
+start|run <config>  start arvbox container
+open       open arvbox workbench in a web browser
+shell      enter arvbox shell
+ip         print arvbox docker container ip address
+host       print arvbox published host
+status     print some information about current arvbox
+stop       stop arvbox container
+restart <config>  stop, then run again
+rebuild  <config>  stop, build arvbox Docker image, run
+reset      delete arvbox arvados data (be careful!)
+destroy    delete all arvbox code and data (be careful!)
+log <service> tail log of specified service
+ls <options>  list directories inside arvbox
+cat <files>   get contents of files inside arvbox
+pipe       run a bash script piped in from stdin
+sv <start|stop|restart> <service> change state of service inside arvbox
+clone <from> <to>   clone an arvbox
+</pre>
+
+h2. Configs
+
+h3. dev
+
+Development configuration.  Boots a complete Arvados environment inside the container.  The "arvados", "arvado-dev" and "sso-devise-omniauth-provider" code directories along data directories "postgres", "var", "passenger" and "gems" are bind mounted from the host file system for easy access and persistence across container rebuilds.  Services are bound to the Docker container's network IP address and can only be accessed on the local host.
+
+In "dev" mode, you can override the default autogenerated settings of Rails projects by adding "application.yml.override" to any Rails project (sso, api, workbench).  This can be used to test out API server settings or point Workbench at an alternate API server.
+
+h3. localdemo
+
+Demo configuration.  Boots a complete Arvados environment inside the container. Unlike the development configuration, code directories are included in the demo image, and data directories are stored in a separate data volume container. Services are bound to the Docker container's network IP address and can only be accessed on the local host.
+
+h3. test
+
+Run the test suite.
+
+h3. publicdev
+
+Publicly accessible development configuration.  Similar to 'dev' except that service ports are published to the host's IP address and can accessed by anyone who can connect to the host system.  See below for more information.  WARNING! The public arvbox configuration is NOT SECURE and must not be placed on a public IP address or used for production work.
+
+h3. publicdemo
+
+Publicly accessible development configuration.  Similar to 'localdemo' except that service ports are published to the host's IP address and can accessed by anyone who can connect to the host system.  See below for more information.  WARNING! The public arvbox configuration is NOT SECURE and must not be placed on a public IP address or used for production work.
+
+h2. Environment variables
+
+h3. ARVBOX_DOCKER
+
+The location of Dockerfile.base and associated files used by "arvbox build".
+default: result of $(readlink -f $(dirname $0)/../lib/arvbox/docker)
+
+h3. ARVBOX_CONTAINER
+
+The name of the Docker container to manipulate.
+default: arvbox
+
+h3. ARVBOX_BASE
+
+The base directory to store persistent data for arvbox containers.
+default: $HOME/.arvbox
+
+h3. ARVBOX_DATA
+
+The base directory to store persistent data for the current container.
+default: $ARVBOX_BASE/$ARVBOX_CONTAINER
+
+h3. ARVADOS_ROOT
+
+The root directory of the Arvados source tree
+default: $ARVBOX_DATA/arvados
+
+h3. ARVADOS_DEV_ROOT
+
+The root directory of the Arvados-dev source tree
+default: $ARVBOX_DATA/arvados-dev
+
+h3. SSO_ROOT
+
+The root directory of the SSO source tree
+default: $ARVBOX_DATA/sso-devise-omniauth-provider
+
+h3. ARVBOX_PUBLISH_IP
+
+The IP address on which to publish services when running in public configuration.  Overrides default detection of the host's IP address.
+
+h2. Using Arvbox for Arvados development
+
+The "Arvbox section of Hacking Arvados":https://dev.arvados.org/projects/arvados/wiki/Arvbox has information about using Arvbox for Arvados development.
+
+h2. Making Arvbox accessible from other hosts
+
+In "dev" and "localdemo" mode, Arvbox can only be accessed on the same host it is running.  To publish Arvbox service ports to the host's service ports and advertise the host's IP address for services, use @publicdev@ or @publicdemo@:
+
+<pre>
+$ arvbox rebuild publicdemo
+</pre>
+
+This attempts to auto-detect the correct IP address to use by taking the IP address of the default route device.  If the auto-detection is wrong, you want to publish a hostname instead of a raw address, or you need to access it through a different device (such as a router or firewall), set @ARVBOX_PUBLISH_IP@ to the desire hostname or IP address.
+
+<pre>
+$ export ARVBOX_PUBLISH_IP=example.com
+$ arvbox rebuild publicdemo
+</pre>
+
+Note: this expects to bind the host's port 80 (http) for workbench, so you cannot have a conflicting web server already running on the host.  It does not attempt to take bind the host's port 22 (ssh), as a result the arvbox ssh port is not published.
+
+h2. Notes
+
+Services are designed to install and auto-configure on start or restart.  For example, the service script for keepstore always compiles keepstore from source and registers the daemon with the API server.
+
+Services are run with process supervision, so a service which exits will be restarted.  Dependencies between services are handled by repeatedly trying and failing the service script until dependencies are fulfilled (by other service scripts) enabling the service script to complete.
index edd2d854f0914a12b073f7ca6f4889b81fbbe339..7580318077e29b9ae7025a39c2203f9b55146ac6 100644 (file)
@@ -8,4 +8,5 @@ Arvados components run on GNU/Linux systems, and do not depend on any particular
 
 Arvados components can be installed and configured in a number of different ways.  Step-by-step instructions are available to perform a production installation from packages with manual configuration.  This method assumes you have several (virtual) machines at your disposal for running the various Arvados components.
 
+* "Docker quick start":arvbox.html
 * "Manual installation":install-manual-prerequisites.html
index 0e5be9411a28f435a738e694dd6c398b91ae2b1e..fda0769abfe6c34d94e0359ce395d2e6bf9b0d48 100644 (file)
@@ -184,6 +184,10 @@ export CRUNCH_DISPATCH_LOCKFILE=/var/lock/crunch-dispatch
 export HOME=$(pwd)
 export RAILS_ENV=production
 
+## Uncomment and edit this line if your compute nodes have cgroup info
+## somewhere other than /sys/fs/cgroup (e.g., "/cgroup" for CentOS 6)
+#export CRUNCH_CGROUP_ROOT="/sys/fs/cgroup"
+
 ## Uncomment this line if your cluster uses self-signed SSL certificates:
 #export ARVADOS_API_HOST_INSECURE=yes
 
index ae210a6f447e42d69ecd9302f414866bb4da6e23..7c2855c5ea72829d0e8a8cd73a849b6feaa44c56 100755 (executable)
@@ -126,6 +126,7 @@ my $jobspec;
 my $job_api_token;
 my $no_clear_tmp;
 my $resume_stash;
+my $cgroup_root = "/sys/fs/cgroup";
 my $docker_bin = "docker.io";
 my $docker_run_args = "";
 GetOptions('force-unlock' => \$force_unlock,
@@ -134,6 +135,7 @@ GetOptions('force-unlock' => \$force_unlock,
            'job-api-token=s' => \$job_api_token,
            'no-clear-tmp' => \$no_clear_tmp,
            'resume-stash=s' => \$resume_stash,
+           'cgroup-root=s' => \$cgroup_root,
            'docker-bin=s' => \$docker_bin,
            'docker-run-args=s' => \$docker_run_args,
     );
@@ -183,11 +185,12 @@ if (($Job || $local_job)->{docker_image_locator}) {
   $cmd = [$docker_bin, 'ps', '-q'];
 }
 Log(undef, "Sanity check is `@$cmd`");
-srun(["srun", "--nodes=\Q$ENV{SLURM_NNODES}\E", "--ntasks-per-node=1"],
-     $cmd,
-     {fork => 1});
-if ($? != 0) {
-  Log(undef, "Sanity check failed: ".exit_status_s($?));
+my ($exited, $stdout, $stderr) = srun_sync(
+  ["srun", "--nodes=\Q$ENV{SLURM_NNODES}\E", "--ntasks-per-node=1"],
+  $cmd,
+  {label => "sanity check"});
+if ($exited != 0) {
+  Log(undef, "Sanity check failed: ".exit_status_s($exited));
   exit EX_TEMPFAIL;
 }
 Log(undef, "Sanity check OK");
@@ -386,28 +389,17 @@ my $nodelist = join(",", @node);
 my $git_tar_count = 0;
 
 if (!defined $no_clear_tmp) {
-  # Clean out crunch_tmp/work, crunch_tmp/opt, crunch_tmp/src*
-  Log (undef, "Clean work dirs");
-
-  my $cleanpid = fork();
-  if ($cleanpid == 0)
-  {
-    # Find FUSE mounts under $CRUNCH_TMP and unmount them.
-    # Then clean up work directories.
-    # TODO: When #5036 is done and widely deployed, we can limit mount's
-    # -t option to simply fuse.keep.
-    srun (["srun", "--nodelist=$nodelist", "-D", $ENV{'TMPDIR'}],
-          ['bash', '-ec', '-o', 'pipefail', 'mount -t fuse,fuse.keep | awk "(index(\$3, \"$CRUNCH_TMP\") == 1){print \$3}" | xargs -r -n 1 fusermount -u -z; sleep 1; rm -rf $JOB_WORK $CRUNCH_INSTALL $CRUNCH_TMP/task $CRUNCH_TMP/src* $CRUNCH_TMP/*.cid']);
-    exit (1);
-  }
-  while (1)
-  {
-    last if $cleanpid == waitpid (-1, WNOHANG);
-    freeze_if_want_freeze ($cleanpid);
-    select (undef, undef, undef, 0.1);
-  }
-  if ($?) {
-    Log(undef, "Clean work dirs: exit ".exit_status_s($?));
+  # Find FUSE mounts under $CRUNCH_TMP and unmount them.  Then clean
+  # up work directories crunch_tmp/work, crunch_tmp/opt,
+  # crunch_tmp/src*.
+  #
+  # TODO: When #5036 is done and widely deployed, we can limit mount's
+  # -t option to simply fuse.keep.
+  my ($exited, $stdout, $stderr) = srun_sync(
+    ["srun", "--nodelist=$nodelist", "-D", $ENV{'TMPDIR'}],
+    ['bash', '-ec', '-o', 'pipefail', 'mount -t fuse,fuse.keep | awk "(index(\$3, \"$CRUNCH_TMP\") == 1){print \$3}" | xargs -r -n 1 fusermount -u -z; sleep 1; rm -rf $JOB_WORK $CRUNCH_INSTALL $CRUNCH_TMP/task $CRUNCH_TMP/src* $CRUNCH_TMP/*.cid'],
+    {label => "clean work dirs"});
+  if ($exited != 0) {
     exit(EX_RETRY_UNLOCKED);
   }
 }
@@ -428,30 +420,22 @@ if ! $docker_bin images -q --no-trunc --all | grep -qxF \Q$docker_hash\E; then
     arv-get \Q$docker_locator$docker_stream/$docker_hash.tar\E | $docker_bin load
 fi
 };
-  my $docker_pid = fork();
-  if ($docker_pid == 0)
-  {
-    srun (["srun", "--nodelist=" . join(',', @node)],
-          ["/bin/bash", "-o", "pipefail", "-ec", $docker_install_script]);
-    exit ($?);
-  }
-  while (1)
-  {
-    last if $docker_pid == waitpid (-1, WNOHANG);
-    freeze_if_want_freeze ($docker_pid);
-    select (undef, undef, undef, 0.1);
-  }
-  if ($? != 0)
+
+  my ($exited, $stdout, $stderr) = srun_sync(
+    ["srun", "--nodelist=" . join(',', @node)],
+    ["/bin/bash", "-o", "pipefail", "-ec", $docker_install_script],
+    {label => "load docker image"});
+  if ($exited != 0)
   {
-    Log(undef, "Installing Docker image from $docker_locator exited " . exit_status_s($?));
     exit(EX_RETRY_UNLOCKED);
   }
 
   # Determine whether this version of Docker supports memory+swap limits.
-  srun(["srun", "--nodelist=" . $node[0]],
-       ["/bin/sh", "-ec", "$docker_bin run --help | grep -qe --memory-swap="],
-      {fork => 1});
-  $docker_limitmem = ($? == 0);
+  ($exited, $stdout, $stderr) = srun_sync(
+    ["srun", "--nodes=1"],
+    [$docker_bin, 'run', '--help'],
+    {label => "check --memory-swap feature"});
+  $docker_limitmem = ($stdout =~ /--memory-swap/);
 
   # Find a non-root Docker user to use.
   # Tries the default user for the container, then 'crunch', then 'nobody',
@@ -461,20 +445,22 @@ fi
   # Docker containers.
   my @tryusers = ("", "crunch", "nobody");
   foreach my $try_user (@tryusers) {
+    my $label;
     my $try_user_arg;
     if ($try_user eq "") {
-      Log(undef, "Checking if container default user is not UID 0");
+      $label = "check whether default user is UID 0";
       $try_user_arg = "";
     } else {
-      Log(undef, "Checking if user '$try_user' is not UID 0");
+      $label = "check whether user '$try_user' is UID 0";
       $try_user_arg = "--user=$try_user";
     }
-    srun(["srun", "--nodelist=" . $node[0]],
-         ["/bin/sh", "-ec",
-          "a=`$docker_bin run $docker_run_args $try_user_arg $docker_hash id --user` && " .
-          " test \$a -ne 0"],
-         {fork => 1});
-    if ($? == 0) {
+    my ($exited, $stdout, $stderr) = srun_sync(
+      ["srun", "--nodes=1"],
+      ["/bin/sh", "-ec",
+       "$docker_bin run $docker_run_args $try_user_arg $docker_hash id --user"],
+      {label => $label});
+    chomp($stdout);
+    if ($exited == 0 && $stdout =~ /^\d+$/ && $stdout > 0) {
       $dockeruserarg = $try_user_arg;
       if ($try_user eq "") {
         Log(undef, "Container will run with default user");
@@ -664,11 +650,9 @@ if (!defined $git_archive) {
   }
 }
 else {
-  my $install_exited;
+  my $exited;
   my $install_script_tries_left = 3;
   for (my $attempts = 0; $attempts < 3; $attempts++) {
-    Log(undef, "Run install script on all workers");
-
     my @srunargs = ("srun",
                     "--nodelist=$nodelist",
                     "-D", $ENV{'TMPDIR'}, "--job-name=$job_id");
@@ -676,59 +660,21 @@ else {
                     "mkdir -p $ENV{CRUNCH_INSTALL} && cd $ENV{CRUNCH_TMP} && perl -");
 
     $ENV{"CRUNCH_GIT_ARCHIVE_HASH"} = md5_hex($git_archive);
-    my ($install_stderr_r, $install_stderr_w);
-    pipe $install_stderr_r, $install_stderr_w or croak("pipe() failed: $!");
-    set_nonblocking($install_stderr_r);
-    my $installpid = fork();
-    if ($installpid == 0)
-    {
-      close($install_stderr_r);
-      fcntl($install_stderr_w, F_SETFL, 0) or croak($!); # no close-on-exec
-      open(STDOUT, ">&", $install_stderr_w);
-      open(STDERR, ">&", $install_stderr_w);
-      srun (\@srunargs, \@execargs, {}, $build_script . $git_archive);
-      exit (1);
-    }
-    close($install_stderr_w);
-    # Tell freeze_if_want_freeze how to kill the child, otherwise the
-    # "waitpid(installpid)" loop won't get interrupted by a freeze:
-    $proc{$installpid} = {};
-    my $stderr_buf = '';
-    # Track whether anything appears on stderr other than slurm errors
-    # ("srun: ...") and the "starting: ..." message printed by the
-    # srun subroutine itself:
+    my ($stdout, $stderr);
+    ($exited, $stdout, $stderr) = srun_sync(
+      \@srunargs, \@execargs,
+      {label => "run install script on all workers"},
+      $build_script . $git_archive);
+
     my $stderr_anything_from_script = 0;
-    my $match_our_own_errors = '^(srun: error: |starting: \[)';
-    while ($installpid != waitpid(-1, WNOHANG)) {
-      freeze_if_want_freeze ($installpid);
-      # Wait up to 0.1 seconds for something to appear on stderr, then
-      # do a non-blocking read.
-      my $bits = fhbits($install_stderr_r);
-      select ($bits, undef, $bits, 0.1);
-      if (0 < sysread ($install_stderr_r, $stderr_buf, 8192, length($stderr_buf)))
-      {
-        while ($stderr_buf =~ /^(.*?)\n/) {
-          my $line = $1;
-          substr $stderr_buf, 0, 1+length($line), "";
-          Log(undef, "stderr $line");
-          if ($line !~ /$match_our_own_errors/) {
-            $stderr_anything_from_script = 1;
-          }
-        }
-      }
-    }
-    delete $proc{$installpid};
-    $install_exited = $?;
-    close($install_stderr_r);
-    if (length($stderr_buf) > 0) {
-      if ($stderr_buf !~ /$match_our_own_errors/) {
+    for my $line (split(/\n/, $stderr)) {
+      if ($line !~ /^(srun: error: |starting: \[)/) {
         $stderr_anything_from_script = 1;
       }
-      Log(undef, "stderr $stderr_buf")
     }
 
-    Log (undef, "Install script exited ".exit_status_s($install_exited));
-    last if $install_exited == 0 || $main::please_freeze;
+    last if $exited == 0 || $main::please_freeze;
+
     # If the install script fails but doesn't print an error message,
     # the next thing anyone is likely to do is just run it again in
     # case it was a transient problem like "slurm communication fails
@@ -744,7 +690,7 @@ else {
     unlink($tar_filename);
   }
 
-  if ($install_exited != 0) {
+  if ($exited != 0) {
     croak("Giving up");
   }
 }
@@ -803,6 +749,7 @@ if ($initial_tasks_this_level < @node) {
   @freeslot = (0..$#slot);
 }
 my $round_num_freeslots = scalar(@freeslot);
+print STDERR "crunch-job have ${round_num_freeslots} free slots for ${initial_tasks_this_level} initial tasks at this level, ".scalar(@node)." nodes, and ".scalar(@slot)." slots\n";
 
 my %round_max_slots = ();
 for (my $ii = $#freeslot; $ii >= 0; $ii--) {
@@ -915,7 +862,7 @@ for (my $todo_ptr = 0; $todo_ptr <= $#jobstep_todo; $todo_ptr ++)
     {
       my $containername = "$Jobstep->{arvados_task}->{uuid}-$Jobstep->{failures}";
       my $cidfile = "$ENV{CRUNCH_TMP}/$containername.cid";
-      $command .= "crunchstat -cgroup-root=/sys/fs/cgroup -cgroup-parent=docker -cgroup-cid=$cidfile -poll=10000 ";
+      $command .= "crunchstat -cgroup-root=\Q$cgroup_root\E -cgroup-parent=docker -cgroup-cid=$cidfile -poll=10000 ";
       $command .= "$docker_bin run $docker_run_args --name=$containername --attach=stdout --attach=stderr --attach=stdin -i \Q$dockeruserarg\E --cidfile=$cidfile --sig-proxy ";
       # We only set memory limits if Docker lets us limit both memory and swap.
       # Memory limits alone have been supported longer, but subprocesses tend
@@ -995,7 +942,7 @@ for (my $todo_ptr = 0; $todo_ptr <= $#jobstep_todo; $todo_ptr ++)
       }
     } else {
       # Non-docker run
-      $command .= "crunchstat -cgroup-root=/sys/fs/cgroup -poll=10000 ";
+      $command .= "crunchstat -cgroup-root=\Q$cgroup_root\E -poll=10000 ";
       $command .= $stdbuf;
       $command .= "perl - $ENV{CRUNCH_SRC}/crunch_scripts/" . $Job->{"script"};
     }
@@ -1013,11 +960,12 @@ for (my $todo_ptr = 0; $todo_ptr <= $#jobstep_todo; $todo_ptr ++)
     next;
   }
   shift @freeslot;
-  $proc{$childpid} = { jobstep => $id,
-                      time => time,
-                      slot => $childslot,
-                      jobstepname => "$job_id.$id.$childpid",
-                    };
+  $proc{$childpid} = {
+    jobstepidx => $id,
+    time => time,
+    slot => $childslot,
+    jobstepname => "$job_id.$id.$childpid",
+  };
   croak ("assert failed: \$slot[$childslot]->{'pid'} exists") if exists $slot[$childslot]->{pid};
   $slot[$childslot]->{pid} = $childpid;
 
@@ -1185,109 +1133,126 @@ sub update_progress_stats
 
 sub reapchildren
 {
-  my $pid = waitpid (-1, WNOHANG);
-  return 0 if $pid <= 0;
-
-  my $whatslot = ($slot[$proc{$pid}->{slot}]->{node}->{name}
-                 . "."
-                 . $slot[$proc{$pid}->{slot}]->{cpu});
-  my $jobstepid = $proc{$pid}->{jobstep};
-  my $elapsed = time - $proc{$pid}->{time};
-  my $Jobstep = $jobstep[$jobstepid];
-
-  my $childstatus = $?;
-  my $exitvalue = $childstatus >> 8;
-  my $exitinfo = "exit ".exit_status_s($childstatus);
-  $Jobstep->{'arvados_task'}->reload;
-  my $task_success = $Jobstep->{'arvados_task'}->{success};
-
-  Log ($jobstepid, "child $pid on $whatslot $exitinfo success=$task_success");
-
-  if (!defined $task_success) {
-    # task did not indicate one way or the other --> fail
-    Log($jobstepid, sprintf(
-          "ERROR: Task process exited %s, but never updated its task record to indicate success and record its output.",
-          exit_status_s($childstatus)));
-    $Jobstep->{'arvados_task'}->{success} = 0;
-    $Jobstep->{'arvados_task'}->save;
-    $task_success = 0;
-  }
+  my $children_reaped = 0;
+  my @successful_task_uuids = ();
 
-  if (!$task_success)
+  while((my $pid = waitpid (-1, WNOHANG)) > 0)
   {
-    my $temporary_fail;
-    $temporary_fail ||= $Jobstep->{tempfail};
-    $temporary_fail ||= ($exitvalue == TASK_TEMPFAIL);
-
-    ++$thisround_failed;
-    ++$thisround_failed_multiple if $Jobstep->{'failures'} >= 1;
-
-    # Check for signs of a failed or misconfigured node
-    if (++$slot[$proc{$pid}->{slot}]->{node}->{losing_streak} >=
-       2+$slot[$proc{$pid}->{slot}]->{node}->{ncpus}) {
-      # Don't count this against jobstep failure thresholds if this
-      # node is already suspected faulty and srun exited quickly
-      if ($slot[$proc{$pid}->{slot}]->{node}->{hold_until} &&
-         $elapsed < 5) {
-       Log ($jobstepid, "blaming failure on suspect node " .
-             $slot[$proc{$pid}->{slot}]->{node}->{name});
-        $temporary_fail ||= 1;
-      }
-      ban_node_by_slot($proc{$pid}->{slot});
+    my $childstatus = $?;
+
+    my $whatslot = ($slot[$proc{$pid}->{slot}]->{node}->{name}
+                    . "."
+                    . $slot[$proc{$pid}->{slot}]->{cpu});
+    my $jobstepidx = $proc{$pid}->{jobstepidx};
+
+    if (!WIFEXITED($childstatus))
+    {
+      # child did not exit (may be temporarily stopped)
+      Log ($jobstepidx, "child $pid did not actually exit in reapchildren, ignoring for now.");
+      next;
     }
 
-    Log ($jobstepid, sprintf('failure (#%d, %s) after %d seconds',
-                             ++$Jobstep->{'failures'},
-                             $temporary_fail ? 'temporary' : 'permanent',
-                             $elapsed));
+    $children_reaped++;
+    my $elapsed = time - $proc{$pid}->{time};
+    my $Jobstep = $jobstep[$jobstepidx];
+
+    my $exitvalue = $childstatus >> 8;
+    my $exitinfo = "exit ".exit_status_s($childstatus);
+    $Jobstep->{'arvados_task'}->reload;
+    my $task_success = $Jobstep->{'arvados_task'}->{success};
+
+    Log ($jobstepidx, "child $pid on $whatslot $exitinfo success=$task_success");
+
+    if (!defined $task_success) {
+      # task did not indicate one way or the other --> fail
+      Log($jobstepidx, sprintf(
+            "ERROR: Task process exited %s, but never updated its task record to indicate success and record its output.",
+            exit_status_s($childstatus)));
+      $Jobstep->{'arvados_task'}->{success} = 0;
+      $Jobstep->{'arvados_task'}->save;
+      $task_success = 0;
+    }
 
-    if (!$temporary_fail || $Jobstep->{'failures'} >= 3) {
-      # Give up on this task, and the whole job
-      $main::success = 0;
+    if (!$task_success)
+    {
+      my $temporary_fail;
+      $temporary_fail ||= $Jobstep->{tempfail};
+      $temporary_fail ||= ($exitvalue == TASK_TEMPFAIL);
+
+      ++$thisround_failed;
+      ++$thisround_failed_multiple if $Jobstep->{'failures'} >= 1;
+
+      # Check for signs of a failed or misconfigured node
+      if (++$slot[$proc{$pid}->{slot}]->{node}->{losing_streak} >=
+          2+$slot[$proc{$pid}->{slot}]->{node}->{ncpus}) {
+        # Don't count this against jobstep failure thresholds if this
+        # node is already suspected faulty and srun exited quickly
+        if ($slot[$proc{$pid}->{slot}]->{node}->{hold_until} &&
+            $elapsed < 5) {
+          Log ($jobstepidx, "blaming failure on suspect node " .
+               $slot[$proc{$pid}->{slot}]->{node}->{name});
+          $temporary_fail ||= 1;
+        }
+        ban_node_by_slot($proc{$pid}->{slot});
+      }
+
+      Log ($jobstepidx, sprintf('failure (#%d, %s) after %d seconds',
+                                ++$Jobstep->{'failures'},
+                                $temporary_fail ? 'temporary' : 'permanent',
+                                $elapsed));
+
+      if (!$temporary_fail || $Jobstep->{'failures'} >= 3) {
+        # Give up on this task, and the whole job
+        $main::success = 0;
+      }
+      # Put this task back on the todo queue
+      push @jobstep_todo, $jobstepidx;
+      $Job->{'tasks_summary'}->{'failed'}++;
     }
-    # Put this task back on the todo queue
-    push @jobstep_todo, $jobstepid;
-    $Job->{'tasks_summary'}->{'failed'}++;
+    else # task_success
+    {
+      push @successful_task_uuids, $Jobstep->{'arvados_task'}->{uuid};
+      ++$thisround_succeeded;
+      $slot[$proc{$pid}->{slot}]->{node}->{losing_streak} = 0;
+      $slot[$proc{$pid}->{slot}]->{node}->{hold_until} = 0;
+      $slot[$proc{$pid}->{slot}]->{node}->{fail_count} = 0;
+      push @jobstep_done, $jobstepidx;
+      Log ($jobstepidx, "success in $elapsed seconds");
+    }
+    $Jobstep->{exitcode} = $childstatus;
+    $Jobstep->{finishtime} = time;
+    $Jobstep->{'arvados_task'}->{finished_at} = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime($Jobstep->{finishtime});
+    $Jobstep->{'arvados_task'}->save;
+    process_stderr_final ($jobstepidx);
+    Log ($jobstepidx, sprintf("task output (%d bytes): %s",
+                              length($Jobstep->{'arvados_task'}->{output}),
+                              $Jobstep->{'arvados_task'}->{output}));
+
+    close $reader{$jobstepidx};
+    delete $reader{$jobstepidx};
+    delete $slot[$proc{$pid}->{slot}]->{pid};
+    push @freeslot, $proc{$pid}->{slot};
+    delete $proc{$pid};
+
+    $progress_is_dirty = 1;
   }
-  else
+
+  if (scalar(@successful_task_uuids) > 0)
   {
-    ++$thisround_succeeded;
-    $slot[$proc{$pid}->{slot}]->{node}->{losing_streak} = 0;
-    $slot[$proc{$pid}->{slot}]->{node}->{hold_until} = 0;
-    $slot[$proc{$pid}->{slot}]->{node}->{fail_count} = 0;
-    push @jobstep_done, $jobstepid;
-    Log ($jobstepid, "success in $elapsed seconds");
-  }
-  $Jobstep->{exitcode} = $childstatus;
-  $Jobstep->{finishtime} = time;
-  $Jobstep->{'arvados_task'}->{finished_at} = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime($Jobstep->{finishtime});
-  $Jobstep->{'arvados_task'}->save;
-  process_stderr ($jobstepid, $task_success);
-  Log ($jobstepid, sprintf("task output (%d bytes): %s",
-                           length($Jobstep->{'arvados_task'}->{output}),
-                           $Jobstep->{'arvados_task'}->{output}));
-
-  close $reader{$jobstepid};
-  delete $reader{$jobstepid};
-  delete $slot[$proc{$pid}->{slot}]->{pid};
-  push @freeslot, $proc{$pid}->{slot};
-  delete $proc{$pid};
-
-  if ($task_success) {
+    Log (undef, sprintf("%d tasks exited (%d succeeded), checking for new tasks from API server.", $children_reaped, scalar(@successful_task_uuids)));
     # Load new tasks
     my $newtask_list = [];
     my $newtask_results;
     do {
       $newtask_results = api_call(
         "job_tasks/list",
-        'where' => {
-          'created_by_job_task_uuid' => $Jobstep->{'arvados_task'}->{uuid}
-        },
+        'filters' => [["created_by_job_task_uuid","in",\@successful_task_uuids]],
         'order' => 'qsequence',
         'offset' => scalar(@$newtask_list),
-      );
+          );
       push(@$newtask_list, @{$newtask_results->{items}});
     } while (@{$newtask_results->{items}});
+    Log (undef, sprintf("Got %d new tasks from API server.", scalar(@$newtask_list)));
     foreach my $arvados_task (@$newtask_list) {
       my $jobstep = {
         'level' => $arvados_task->{'sequence'},
@@ -1299,14 +1264,16 @@ sub reapchildren
     }
   }
 
-  $progress_is_dirty = 1;
-  1;
+  return $children_reaped;
 }
 
 sub check_refresh_wanted
 {
   my @stat = stat $ENV{"CRUNCH_REFRESH_TRIGGER"};
-  if (@stat && $stat[9] > $latest_refresh) {
+  if (@stat &&
+      $stat[9] > $latest_refresh &&
+      # ...and we have actually locked the job record...
+      $job_id eq $Job->{'uuid'}) {
     $latest_refresh = scalar time;
     my $Job2 = api_call("jobs/get", uuid => $jobspec);
     for my $attr ('cancelled_at',
@@ -1344,10 +1311,13 @@ sub check_squeue
   # squeue check interval (15s) this should make the squeue check an
   # infrequent event.
   my $silent_procs = 0;
-  for my $procinfo (values %proc)
+  for my $js (map {$jobstep[$_->{jobstepidx}]} values %proc)
   {
-    my $jobstep = $jobstep[$procinfo->{jobstep}];
-    if ($jobstep->{stderr_at} < $last_squeue_check)
+    if (!exists($js->{stderr_at}))
+    {
+      $js->{stderr_at} = 0;
+    }
+    if ($js->{stderr_at} < $last_squeue_check)
     {
       $silent_procs++;
     }
@@ -1357,16 +1327,16 @@ sub check_squeue
   # use killem() on procs whose killtime is reached
   while (my ($pid, $procinfo) = each %proc)
   {
-    my $jobstep = $jobstep[$procinfo->{jobstep}];
+    my $js = $jobstep[$procinfo->{jobstepidx}];
     if (exists $procinfo->{killtime}
         && $procinfo->{killtime} <= time
-        && $jobstep->{stderr_at} < $last_squeue_check)
+        && $js->{stderr_at} < $last_squeue_check)
     {
       my $sincewhen = "";
-      if ($jobstep->{stderr_at}) {
-        $sincewhen = " in last " . (time - $jobstep->{stderr_at}) . "s";
+      if ($js->{stderr_at}) {
+        $sincewhen = " in last " . (time - $js->{stderr_at}) . "s";
       }
-      Log($procinfo->{jobstep}, "killing orphaned srun process $pid (task not in slurm queue, no stderr received$sincewhen)");
+      Log($procinfo->{jobstepidx}, "killing orphaned srun process $pid (task not in slurm queue, no stderr received$sincewhen)");
       killem ($pid);
     }
   }
@@ -1416,7 +1386,7 @@ sub check_squeue
       # error/delay has caused the task to die without notifying srun,
       # and we'll kill srun ourselves.
       $procinfo->{killtime} = time + 30;
-      Log($procinfo->{jobstep}, "notice: task is not in slurm queue but srun process $pid has not exited");
+      Log($procinfo->{jobstepidx}, "notice: task is not in slurm queue but srun process $pid has not exited");
     }
   }
 }
@@ -1435,70 +1405,99 @@ sub release_allocation
 sub readfrompipes
 {
   my $gotsome = 0;
-  foreach my $job (keys %reader)
+  my %fd_job;
+  my $sel = IO::Select->new();
+  foreach my $jobstepidx (keys %reader)
+  {
+    my $fd = $reader{$jobstepidx};
+    $sel->add($fd);
+    $fd_job{$fd} = $jobstepidx;
+
+    if (my $stdout_fd = $jobstep[$jobstepidx]->{stdout_r}) {
+      $sel->add($stdout_fd);
+      $fd_job{$stdout_fd} = $jobstepidx;
+    }
+  }
+  # select on all reader fds with 0.1s timeout
+  my @ready_fds = $sel->can_read(0.1);
+  foreach my $fd (@ready_fds)
   {
     my $buf;
-    if (0 < sysread ($reader{$job}, $buf, 65536))
+    if (0 < sysread ($fd, $buf, 65536))
     {
+      $gotsome = 1;
       print STDERR $buf if $ENV{CRUNCH_DEBUG};
-      $jobstep[$job]->{stderr_at} = time;
-      $jobstep[$job]->{stderr} .= $buf;
+
+      my $jobstepidx = $fd_job{$fd};
+      if ($jobstep[$jobstepidx]->{stdout_r} == $fd) {
+        $jobstep[$jobstepidx]->{stdout_captured} .= $buf;
+        next;
+      }
+
+      $jobstep[$jobstepidx]->{stderr_at} = time;
+      $jobstep[$jobstepidx]->{stderr} .= $buf;
 
       # Consume everything up to the last \n
-      preprocess_stderr ($job);
+      preprocess_stderr ($jobstepidx);
 
-      if (length ($jobstep[$job]->{stderr}) > 16384)
+      if (length ($jobstep[$jobstepidx]->{stderr}) > 16384)
       {
         # If we get a lot of stderr without a newline, chop off the
         # front to avoid letting our buffer grow indefinitely.
-        substr ($jobstep[$job]->{stderr},
-                0, length($jobstep[$job]->{stderr}) - 8192) = "";
+        substr ($jobstep[$jobstepidx]->{stderr},
+                0, length($jobstep[$jobstepidx]->{stderr}) - 8192) = "";
       }
-      $gotsome = 1;
     }
   }
   return $gotsome;
 }
 
 
+# Consume all full lines of stderr for a jobstep. Everything after the
+# last newline will remain in $jobstep[$jobstepidx]->{stderr} after
+# returning.
 sub preprocess_stderr
 {
-  my $job = shift;
+  my $jobstepidx = shift;
 
-  while ($jobstep[$job]->{stderr} =~ /^(.*?)\n/) {
+  while ($jobstep[$jobstepidx]->{stderr} =~ /^(.*?)\n/) {
     my $line = $1;
-    substr $jobstep[$job]->{stderr}, 0, 1+length($line), "";
-    Log ($job, "stderr $line");
+    substr $jobstep[$jobstepidx]->{stderr}, 0, 1+length($line), "";
+    Log ($jobstepidx, "stderr $line");
     if ($line =~ /srun: error: (SLURM job $ENV{SLURM_JOB_ID} has expired|Unable to confirm allocation for job $ENV{SLURM_JOB_ID})/) {
       # whoa.
       $main::please_freeze = 1;
     }
+    elsif (!exists $jobstep[$jobstepidx]->{slotindex}) {
+      # Skip the following tempfail checks if this srun proc isn't
+      # attached to a particular worker slot.
+    }
     elsif ($line =~ /srun: error: (Node failure on|Aborting, .*\bio error\b)/) {
-      my $job_slot_index = $jobstep[$job]->{slotindex};
+      my $job_slot_index = $jobstep[$jobstepidx]->{slotindex};
       $slot[$job_slot_index]->{node}->{fail_count}++;
-      $jobstep[$job]->{tempfail} = 1;
+      $jobstep[$jobstepidx]->{tempfail} = 1;
       ban_node_by_slot($job_slot_index);
     }
     elsif ($line =~ /srun: error: (Unable to create job step|.*: Communication connection failure)/) {
-      $jobstep[$job]->{tempfail} = 1;
-      ban_node_by_slot($jobstep[$job]->{slotindex});
+      $jobstep[$jobstepidx]->{tempfail} = 1;
+      ban_node_by_slot($jobstep[$jobstepidx]->{slotindex});
     }
-    elsif ($line =~ /arvados\.errors\.Keep/) {
-      $jobstep[$job]->{tempfail} = 1;
+    elsif ($line =~ /\bKeep(Read|Write|Request)Error:/) {
+      $jobstep[$jobstepidx]->{tempfail} = 1;
     }
   }
 }
 
 
-sub process_stderr
+sub process_stderr_final
 {
-  my $job = shift;
-  my $task_success = shift;
-  preprocess_stderr ($job);
+  my $jobstepidx = shift;
+  preprocess_stderr ($jobstepidx);
 
   map {
-    Log ($job, "stderr $_");
-  } split ("\n", $jobstep[$job]->{stderr});
+    Log ($jobstepidx, "stderr $_");
+  } split ("\n", $jobstep[$jobstepidx]->{stderr});
+  $jobstep[$jobstepidx]->{stderr} = '';
 }
 
 sub fetch_block
@@ -1636,7 +1635,7 @@ sub killem
     }
     if (!exists $proc{$_}->{"sent_$sig"})
     {
-      Log ($proc{$_}->{jobstep}, "sending 2x signal $sig to pid $_");
+      Log ($proc{$_}->{jobstepidx}, "sending 2x signal $sig to pid $_");
       kill $sig, $_;
       select (undef, undef, undef, 0.1);
       if ($sig == 2)
@@ -1760,16 +1759,21 @@ sub log_writer_is_active() {
   return $log_pipe_pid;
 }
 
-sub Log                                # ($jobstep_id, $logmessage)
+sub Log                                # ($jobstepidx, $logmessage)
 {
-  if ($_[1] =~ /\n/) {
+  my ($jobstepidx, $logmessage) = @_;
+  if ($logmessage =~ /\n/) {
     for my $line (split (/\n/, $_[1])) {
-      Log ($_[0], $line);
+      Log ($jobstepidx, $line);
     }
     return;
   }
   my $fh = select STDERR; $|=1; select $fh;
-  my $message = sprintf ("%s %d %s %s", $job_id, $$, @_);
+  my $task_qseq = '';
+  if (defined($jobstepidx) && exists($jobstep[$jobstepidx]->{arvados_task})) {
+    $task_qseq = $jobstepidx;
+  }
+  my $message = sprintf ("%s %d %s %s", $job_id, $$, $task_qseq, $logmessage);
   $message =~ s{([^ -\176])}{"\\" . sprintf ("%03o", ord($1))}ge;
   $message .= "\n";
   my $datetime;
@@ -1893,6 +1897,83 @@ sub freezeunquote
 }
 
 
+sub srun_sync
+{
+  my $srunargs = shift;
+  my $execargs = shift;
+  my $opts = shift || {};
+  my $stdin = shift;
+
+  my $label = exists $opts->{label} ? $opts->{label} : "@$execargs";
+  Log (undef, "$label: start");
+
+  my ($stderr_r, $stderr_w);
+  pipe $stderr_r, $stderr_w or croak("pipe() failed: $!");
+
+  my ($stdout_r, $stdout_w);
+  pipe $stdout_r, $stdout_w or croak("pipe() failed: $!");
+
+  my $srunpid = fork();
+  if ($srunpid == 0)
+  {
+    close($stderr_r);
+    close($stdout_r);
+    fcntl($stderr_w, F_SETFL, 0) or croak($!); # no close-on-exec
+    fcntl($stdout_w, F_SETFL, 0) or croak($!);
+    open(STDERR, ">&", $stderr_w);
+    open(STDOUT, ">&", $stdout_w);
+    srun ($srunargs, $execargs, $opts, $stdin);
+    exit (1);
+  }
+  close($stderr_w);
+  close($stdout_w);
+
+  set_nonblocking($stderr_r);
+  set_nonblocking($stdout_r);
+
+  # Add entries to @jobstep and %proc so check_squeue() and
+  # freeze_if_want_freeze() can treat it like a job task process.
+  push @jobstep, {
+    stderr => '',
+    stderr_at => 0,
+    stderr_captured => '',
+    stdout_r => $stdout_r,
+    stdout_captured => '',
+  };
+  my $jobstepidx = $#jobstep;
+  $proc{$srunpid} = {
+    jobstepidx => $jobstepidx,
+  };
+  $reader{$jobstepidx} = $stderr_r;
+
+  while ($srunpid != waitpid ($srunpid, WNOHANG)) {
+    my $busy = readfrompipes();
+    if (!$busy || ($latest_refresh + 2 < scalar time)) {
+      check_refresh_wanted();
+      check_squeue();
+    }
+    if (!$busy) {
+      select(undef, undef, undef, 0.1);
+    }
+    killem(keys %proc) if $main::please_freeze;
+  }
+  my $exited = $?;
+
+  1 while readfrompipes();
+  process_stderr_final ($jobstepidx);
+
+  Log (undef, "$label: exit ".exit_status_s($exited));
+
+  close($stdout_r);
+  close($stderr_r);
+  delete $proc{$srunpid};
+  delete $reader{$jobstepidx};
+
+  my $j = pop @jobstep;
+  return ($exited, $j->{stdout_captured}, $j->{stderr_captured});
+}
+
+
 sub srun
 {
   my $srunargs = shift;
index 961ac28a9f97888b8230e6bb27e9dabef31f58b0..52a7353f67a9e5f0040b31f083a5f719164a063c 100755 (executable)
@@ -1,3 +1,3 @@
 #!/bin/sh
 echo >&2 Failing mount stub was called
-exit 1
+exit 44
index 22d756a8c81f8ae64d8602925ab25fda9bbe156b..0fbff2e6de670da112461da20107fa5eeaae1e9b 100644 (file)
@@ -91,7 +91,7 @@ class TestCrunchJob < Minitest::Test
       tryjobrecord j, binstubs: ['clean_fail']
     end
     assert_match /Failing mount stub was called/, err
-    assert_match /Clean work dirs: exit 1\n$/, err
+    assert_match /clean work dirs: exit 44\n$/, err
     assert_equal SPECIAL_EXIT[:EX_RETRY_UNLOCKED], $?.exitstatus
   end
 
index 8370e3d5e75a42e68fd73ee770c281b0388dd198..533d24a6981177e2a5d6ba916b367fd0bb1a2e73 100644 (file)
@@ -5,6 +5,8 @@ import arvados
 import arvados.events
 import arvados.commands.keepdocker
 import arvados.commands.run
+import arvados.collection
+import arvados.util
 import cwltool.draft2tool
 import cwltool.workflow
 import cwltool.main
@@ -15,13 +17,24 @@ import fnmatch
 import logging
 import re
 import os
+import sys
 
 from cwltool.process import get_feature
+from arvados.api import OrderedJsonModel
 
 logger = logging.getLogger('arvados.cwl-runner')
 logger.setLevel(logging.INFO)
 
-def arv_docker_get_image(api_client, dockerRequirement, pull_image):
+crunchrunner_pdh = "83db29f08544e1c319572a6bd971088a+140"
+crunchrunner_download = "https://cloud.curoverse.com/collections/download/qr1hi-4zz18-n3m1yxd0vx78jic/1i1u2qtq66k1atziv4ocfgsg5nu5tj11n4r6e0bhvjg03rix4m/crunchrunner"
+certs_download = "https://cloud.curoverse.com/collections/download/qr1hi-4zz18-n3m1yxd0vx78jic/1i1u2qtq66k1atziv4ocfgsg5nu5tj11n4r6e0bhvjg03rix4m/ca-certificates.crt"
+
+tmpdirre = re.compile(r"^\S+ \S+ \d+ \d+ stderr \S+ \S+ crunchrunner: \$\(task\.tmpdir\)=(.*)")
+outdirre = re.compile(r"^\S+ \S+ \d+ \d+ stderr \S+ \S+ crunchrunner: \$\(task\.outdir\)=(.*)")
+keepre = re.compile(r"^\S+ \S+ \d+ \d+ stderr \S+ \S+ crunchrunner: \$\(task\.keep\)=(.*)")
+
+
+def arv_docker_get_image(api_client, dockerRequirement, pull_image, project_uuid):
     if "dockerImageId" not in dockerRequirement and "dockerPull" in dockerRequirement:
         dockerRequirement["dockerImageId"] = dockerRequirement["dockerPull"]
 
@@ -35,10 +48,10 @@ def arv_docker_get_image(api_client, dockerRequirement, pull_image):
 
     if not images:
         imageId = cwltool.docker.get_image(dockerRequirement, pull_image)
-        args = [image_name]
+        args = ["--project-uuid="+project_uuid, image_name]
         if image_tag:
             args.append(image_tag)
-        logger.info("Uploading Docker image %s", ":".join(args))
+        logger.info("Uploading Docker image %s", ":".join(args[1:]))
         arvados.commands.keepdocker.main(args)
 
     return dockerRequirement["dockerImageId"]
@@ -137,14 +150,22 @@ class ArvadosJob(object):
 
         (docker_req, docker_is_req) = get_feature(self, "DockerRequirement")
         if docker_req and kwargs.get("use_container") is not False:
-            runtime_constraints["docker_image"] = arv_docker_get_image(self.arvrunner.api, docker_req, pull_image)
+            runtime_constraints["docker_image"] = arv_docker_get_image(self.arvrunner.api, docker_req, pull_image, self.arvrunner.project_uuid)
+
+        resources = self.builder.resources
+        if resources is not None:
+            runtime_constraints["min_cores_per_node"] = resources.get("cores", 1)
+            runtime_constraints["min_ram_mb_per_node"] = resources.get("ram")
+            runtime_constraints["min_scratch_mb_per_node"] = resources.get("tmpdirSize", 0) + resources.get("outdirSize", 0)
 
         try:
             response = self.arvrunner.api.jobs().create(body={
+                "owner_uuid": self.arvrunner.project_uuid,
                 "script": "crunchrunner",
-                "repository": kwargs["repository"],
+                "repository": "arvados",
                 "script_version": "master",
-                "script_parameters": {"tasks": [script_parameters]},
+                "minimum_script_version": "9e5b98e8f5f4727856b53447191f9c06e3da2ba6",
+                "script_parameters": {"tasks": [script_parameters], "crunchrunner": crunchrunner_pdh+"/crunchrunner"},
                 "runtime_constraints": runtime_constraints
             }, find_or_create=kwargs.get("enable_reuse", True)).execute(num_retries=self.arvrunner.num_retries)
 
@@ -186,6 +207,26 @@ class ArvadosJob(object):
             try:
                 outputs = {}
                 if record["output"]:
+                    logc = arvados.collection.Collection(record["log"])
+                    log = logc.open(logc.keys()[0])
+                    tmpdir = None
+                    outdir = None
+                    keepdir = None
+                    for l in log.readlines():
+                        g = tmpdirre.match(l)
+                        if g:
+                            tmpdir = g.group(1)
+                        g = outdirre.match(l)
+                        if g:
+                            outdir = g.group(1)
+                        g = keepre.match(l)
+                        if g:
+                            keepdir = g.group(1)
+                        if tmpdir and outdir and keepdir:
+                            break
+
+                    self.builder.outdir = outdir
+                    self.builder.pathmapper.keepdir = keepdir
                     outputs = self.collect_outputs("keep:" + record["output"])
             except Exception as e:
                 logger.exception("Got exception while collecting job outputs:")
@@ -223,17 +264,27 @@ class ArvPathMapper(cwltool.pathmapper.PathMapper):
                                              arvrunner.api,
                                              dry_run=kwargs.get("dry_run"),
                                              num_retries=3,
-                                             fnPattern="$(task.keep)/%s/%s")
+                                             fnPattern="$(task.keep)/%s/%s",
+                                             project=arvrunner.project_uuid)
 
         for src, ab, st in uploadfiles:
             arvrunner.add_uploaded(src, (ab, st.fn))
             self._pathmap[src] = (ab, st.fn)
 
+        self.keepdir = None
+
+    def reversemap(self, target):
+        if target.startswith("keep:"):
+            return (target, target)
+        elif self.keepdir and target.startswith(self.keepdir):
+            return (target, "keep:" + target[len(self.keepdir)+1:])
+        else:
+            return super(ArvPathMapper, self).reversemap(target)
 
 
 class ArvadosCommandTool(cwltool.draft2tool.CommandLineTool):
     def __init__(self, arvrunner, toolpath_object, **kwargs):
-        super(ArvadosCommandTool, self).__init__(toolpath_object, outdir="$(task.outdir)", tmpdir="$(task.tmpdir)", **kwargs)
+        super(ArvadosCommandTool, self).__init__(toolpath_object, **kwargs)
         self.arvrunner = arvrunner
 
     def makeJobRunner(self):
@@ -274,24 +325,24 @@ class ArvCwlRunner(object):
 
     def on_message(self, event):
         if "object_uuid" in event:
-                if event["object_uuid"] in self.jobs and event["event_type"] == "update":
-                    if event["properties"]["new_attributes"]["state"] == "Running" and self.jobs[event["object_uuid"]].running is False:
-                        uuid = event["object_uuid"]
-                        with self.lock:
-                            j = self.jobs[uuid]
-                            logger.info("Job %s (%s) is Running", j.name, uuid)
-                            j.running = True
-                            j.update_pipeline_component(event["properties"]["new_attributes"])
-                    elif event["properties"]["new_attributes"]["state"] in ("Complete", "Failed", "Cancelled"):
-                        uuid = event["object_uuid"]
-                        try:
-                            self.cond.acquire()
-                            j = self.jobs[uuid]
-                            logger.info("Job %s (%s) is %s", j.name, uuid, event["properties"]["new_attributes"]["state"])
-                            j.done(event["properties"]["new_attributes"])
-                            self.cond.notify()
-                        finally:
-                            self.cond.release()
+            if event["object_uuid"] in self.jobs and event["event_type"] == "update":
+                if event["properties"]["new_attributes"]["state"] == "Running" and self.jobs[event["object_uuid"]].running is False:
+                    uuid = event["object_uuid"]
+                    with self.lock:
+                        j = self.jobs[uuid]
+                        logger.info("Job %s (%s) is Running", j.name, uuid)
+                        j.running = True
+                        j.update_pipeline_component(event["properties"]["new_attributes"])
+                elif event["properties"]["new_attributes"]["state"] in ("Complete", "Failed", "Cancelled"):
+                    uuid = event["object_uuid"]
+                    try:
+                        self.cond.acquire()
+                        j = self.jobs[uuid]
+                        logger.info("Job %s (%s) is %s", j.name, uuid, event["properties"]["new_attributes"]["state"])
+                        j.done(event["properties"]["new_attributes"])
+                        self.cond.notify()
+                    finally:
+                        self.cond.release()
 
     def get_uploaded(self):
         return self.uploaded.copy()
@@ -302,66 +353,104 @@ class ArvCwlRunner(object):
     def arvExecutor(self, tool, job_order, input_basedir, args, **kwargs):
         events = arvados.events.subscribe(arvados.api('v1'), [["object_uuid", "is_a", "arvados#job"]], self.on_message)
 
-        self.pipeline = self.api.pipeline_instances().create(body={"name": shortname(tool.tool["id"]),
-                                                                   "components": {},
-                                                                   "state": "RunningOnClient"}).execute(num_retries=self.num_retries)
+        try:
+            self.api.collections().get(uuid=crunchrunner_pdh).execute()
+        except arvados.errors.ApiError as e:
+            import httplib2
+            h = httplib2.Http(ca_certs=arvados.util.ca_certs_path())
+            resp, content = h.request(crunchrunner_download, "GET")
+            resp2, content2 = h.request(certs_download, "GET")
+            with arvados.collection.Collection() as col:
+                with col.open("crunchrunner", "w") as f:
+                    f.write(content)
+                with col.open("ca-certificates.crt", "w") as f:
+                    f.write(content2)
+
+                col.save_new("crunchrunner binary", ensure_unique_name=True)
 
         self.fs_access = CollectionFsAccess(input_basedir)
 
         kwargs["fs_access"] = self.fs_access
         kwargs["enable_reuse"] = args.enable_reuse
-        kwargs["repository"] = args.repository
+
+        kwargs["outdir"] = "$(task.outdir)"
+        kwargs["tmpdir"] = "$(task.tmpdir)"
+
+        useruuid = self.api.users().current().execute()["uuid"]
+        self.project_uuid = args.project_uuid if args.project_uuid else useruuid
 
         if kwargs.get("conformance_test"):
             return cwltool.main.single_job_executor(tool, job_order, input_basedir, args, **kwargs)
         else:
+            self.pipeline = self.api.pipeline_instances().create(
+                body={
+                    "owner_uuid": self.project_uuid,
+                    "name": shortname(tool.tool["id"]),
+                    "components": {},
+                    "state": "RunningOnClient"}).execute(num_retries=self.num_retries)
+
+            logger.info("Pipeline instance %s", self.pipeline["uuid"])
+
             jobiter = tool.job(job_order,
-                            input_basedir,
-                            self.output_callback,
-                            **kwargs)
+                               input_basedir,
+                               self.output_callback,
+                               docker_outdir="$(task.outdir)",
+                               **kwargs)
 
-            for runnable in jobiter:
-                if runnable:
-                    with self.lock:
+            try:
+                self.cond.acquire()
+                # Will continue to hold the lock for the duration of this code
+                # except when in cond.wait(), at which point on_message can update
+                # job state and process output callbacks.
+
+                for runnable in jobiter:
+                    if runnable:
                         runnable.run(**kwargs)
-                else:
-                    if self.jobs:
-                        try:
-                            self.cond.acquire()
-                            self.cond.wait()
-                        finally:
-                            self.cond.release()
                     else:
-                        logger.error("Workflow cannot make any more progress.")
-                        break
+                        if self.jobs:
+                            self.cond.wait(1)
+                        else:
+                            logger.error("Workflow is deadlocked, no runnable jobs and not waiting on any pending jobs.")
+                            break
 
-            while self.jobs:
-                try:
-                    self.cond.acquire()
-                    self.cond.wait()
-                finally:
-                    self.cond.release()
+                while self.jobs:
+                    self.cond.wait(1)
 
-            events.close()
+                events.close()
 
-            if self.final_output is None:
-                raise cwltool.workflow.WorkflowException("Workflow did not return a result.")
+                if self.final_output is None:
+                    raise cwltool.workflow.WorkflowException("Workflow did not return a result.")
+
+                # create final output collection
+            except:
+                if sys.exc_info()[0] is KeyboardInterrupt:
+                    logger.error("Interrupted, marking pipeline as failed")
+                else:
+                    logger.exception("Caught unhandled exception, marking pipeline as failed")
+                self.api.pipeline_instances().update(uuid=self.pipeline["uuid"],
+                                                     body={"state": "Failed"}).execute(num_retries=self.num_retries)
+            finally:
+                self.cond.release()
 
             return self.final_output
 
 
 def main(args, stdout, stderr, api_client=None):
-    runner = ArvCwlRunner(api_client=arvados.api('v1'))
     args.insert(0, "--leave-outputs")
     parser = cwltool.main.arg_parser()
     exgroup = parser.add_mutually_exclusive_group()
     exgroup.add_argument("--enable-reuse", action="store_true",
-                        default=False, dest="enable_reuse",
+                        default=True, dest="enable_reuse",
                         help="")
     exgroup.add_argument("--disable-reuse", action="store_false",
-                        default=False, dest="enable_reuse",
+                        default=True, dest="enable_reuse",
                         help="")
+    parser.add_argument("--project-uuid", type=str, help="Project that will own the workflow jobs")
 
-    parser.add_argument('--repository', type=str, default="peter/crunchrunner", help="Repository containing the 'crunchrunner' program.")
+    try:
+        runner = ArvCwlRunner(api_client=arvados.api('v1', model=OrderedJsonModel()))
+    except Exception as e:
+        logger.error(e)
+        return 1
 
     return cwltool.main.main(args, executor=runner.arvExecutor, makeTool=runner.arvMakeTool, parser=parser)
index 65ae16b5158aebe388afc7f42e2e247f4e13733f..3fc7433adfcd054c1e28bcd11acb49807506732e 100644 (file)
@@ -30,9 +30,11 @@ setup(name='arvados-cwl-runner',
           'bin/arvados-cwl-runner'
       ],
       install_requires=[
-          'cwltool>=1.0.20160129152024',
-          'arvados-python-client>=0.1.20160122132348'
+          'cwltool>=1.0.20160311170456',
+          'arvados-python-client>=0.1.20160219154918'
       ],
+      test_suite='tests',
+      tests_require=['mock>=1.0'],
       zip_safe=True,
       cmdclass={'egg_info': tagger},
       )
diff --git a/sdk/cwl/test_with_arvbox.sh b/sdk/cwl/test_with_arvbox.sh
new file mode 100755 (executable)
index 0000000..aef2700
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+if ! which arvbox >/dev/null ; then
+    export PATH=$PATH:$(readlink -f $(dirname $0)/../../tools/arvbox/bin)
+fi
+
+reset_container=1
+leave_running=0
+config=dev
+
+while test -n "$1" ; do
+    arg="$1"
+    case "$arg" in
+        --no-reset-container)
+            reset_container=0
+            shift
+            ;;
+        --leave-running)
+            leave_running=1
+            shift
+            ;;
+        --config)
+            config=$2
+            shift ; shift
+            ;;
+        -*)
+            break
+            ;;
+    esac
+done
+
+if test -z "$ARVBOX_CONTAINER" ; then
+   export ARVBOX_CONTAINER=cwltest
+fi
+
+if test $reset_container = 1 ; then
+    arvbox reset -f
+fi
+
+arvbox start $config
+
+arvbox pipe <<EOF
+set -eu -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/arvados/sdk/cwl
+python setup.py sdist
+pip_install \$(ls dist/arvados-cwl-runner-*.tar.gz | tail -n1)
+
+mkdir -p /tmp/cwltest
+cd /tmp/cwltest
+if ! test -d common-workflow-language ; then
+  git clone https://github.com/common-workflow-language/common-workflow-language.git
+fi
+cd common-workflow-language
+git pull
+export ARVADOS_API_HOST=localhost:8000
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=\$(cat /var/lib/arvados/superuser_token)
+env
+exec ./run_test.sh "$@"
+EOF
+
+CODE=$?
+
+if test $leave_running = 0 ; then
+    arvbox stop
+fi
+
+exit $CODE
diff --git a/sdk/cwl/tests/__init__.py b/sdk/cwl/tests/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/sdk/cwl/tests/test_job.py b/sdk/cwl/tests/test_job.py
new file mode 100644 (file)
index 0000000..56f3110
--- /dev/null
@@ -0,0 +1,81 @@
+import unittest
+import mock
+import arvados_cwl
+
+class TestJob(unittest.TestCase):
+
+    # The test passes no builder.resources
+    # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
+    def test_run(self):
+        runner = mock.MagicMock()
+        runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
+        tool = {
+            "inputs": [],
+            "outputs": [],
+            "baseCommand": "ls"
+        }
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool)
+        arvtool.formatgraph = None
+        for j in arvtool.job({}, "", mock.MagicMock()):
+            j.run()
+        runner.api.jobs().create.assert_called_with(body={
+            'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
+            'runtime_constraints': {},
+            'script_parameters': {
+                'tasks': [{
+                    'task.env': {'TMPDIR': '$(task.tmpdir)'},
+                    'command': ['ls']
+                }],
+                'crunchrunner': '83db29f08544e1c319572a6bd971088a+140/crunchrunner'
+            },
+            'script_version': 'master',
+            'minimum_script_version': '9e5b98e8f5f4727856b53447191f9c06e3da2ba6',
+            'repository': 'arvados',
+            'script': 'crunchrunner',
+            'runtime_constraints': {
+                'min_cores_per_node': 1,
+                'min_ram_mb_per_node': 1024,
+                'min_scratch_mb_per_node': 2048 # tmpdirSize + outdirSize
+            }
+        }, find_or_create=True)
+
+    # The test passes some fields in builder.resources
+    # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
+    def test_resource_requirements(self):
+        runner = mock.MagicMock()
+        runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
+        tool = {
+            "inputs": [],
+            "outputs": [],
+            "hints": [{
+                "class": "ResourceRequirement",
+                "coresMin": 3,
+                "ramMin": 3000,
+                "tmpdirMin": 4000
+            }],
+            "baseCommand": "ls"
+        }
+        arvtool = arvados_cwl.ArvadosCommandTool(runner, tool)
+        arvtool.formatgraph = None
+        for j in arvtool.job({}, "", mock.MagicMock()):
+            j.run()
+        runner.api.jobs().create.assert_called_with(body={
+            'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
+            'runtime_constraints': {},
+            'script_parameters': {
+                'tasks': [{
+                    'task.env': {'TMPDIR': '$(task.tmpdir)'},
+                    'command': ['ls']
+                }],
+                'crunchrunner': '83db29f08544e1c319572a6bd971088a+140/crunchrunner'
+            },
+            'script_version': 'master',
+            'minimum_script_version': '9e5b98e8f5f4727856b53447191f9c06e3da2ba6',
+            'repository': 'arvados',
+            'script': 'crunchrunner',
+            'runtime_constraints': {
+                'min_cores_per_node': 3,
+                'min_ram_mb_per_node': 3000,
+                'min_scratch_mb_per_node': 5024 # tmpdirSize + outdirSize
+            }
+        }, find_or_create=True)
index 8e24e18fda845866909aff7f6bba1bd02234d53c..226cf9122be430d8c08c03c595447a3448a19a22 100644 (file)
@@ -1,13 +1,17 @@
 package main
 
 import (
+       "crypto/x509"
        "fmt"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "io/ioutil"
        "log"
+       "net/http"
        "os"
        "os/exec"
        "os/signal"
+       "path"
        "strings"
        "syscall"
 )
@@ -209,6 +213,10 @@ func runner(api IArvadosClient,
                "$(task.outdir)": outdir,
                "$(task.keep)":   keepmount}
 
+       log.Printf("crunchrunner: $(task.tmpdir)=%v", tmpdir)
+       log.Printf("crunchrunner: $(task.outdir)=%v", outdir)
+       log.Printf("crunchrunner: $(task.keep)=%v", keepmount)
+
        // Set up subprocess
        for k, v := range taskp.Command {
                taskp.Command[k] = substitute(v, replacements)
@@ -317,6 +325,15 @@ func main() {
                log.Fatal(err)
        }
 
+       certpath := path.Join(path.Dir(os.Args[0]), "ca-certificates.crt")
+       certdata, err := ioutil.ReadFile(certpath)
+       if err == nil {
+               log.Printf("Using TLS certificates at %v", certpath)
+               certs := x509.NewCertPool()
+               certs.AppendCertsFromPEM(certdata)
+               api.Client.Transport.(*http.Transport).TLSClientConfig.RootCAs = certs
+       }
+
        jobUuid := os.Getenv("JOB_UUID")
        taskUuid := os.Getenv("TASK_UUID")
        tmpdir := os.Getenv("TASK_WORK")
index e2e8ba19b64823af233c25eba1f90880dc9f4067..f24b1ed8142d3e3678ff819bfe4823ebe2add874 100644 (file)
@@ -207,6 +207,7 @@ def api(version=None, cache=True, host=None, token=None, insecure=False, **kwarg
 
     svc = apiclient_discovery.build('arvados', version, **kwargs)
     svc.api_token = token
+    svc.insecure = insecure
     kwargs['http'].max_request_size = svc._rootDesc.get('maxRequestSize', 0)
     kwargs['http'].cache = None
     return svc
index a10eb2b348aff4499648d6738b8042e5a9d4fa11..2ee97b9867036ded89244d1f11c2294efccb9ef3 100755 (executable)
@@ -24,6 +24,7 @@ import shutil
 import sys
 import logging
 import tempfile
+import urlparse
 
 import arvados
 import arvados.config
@@ -87,6 +88,13 @@ def main():
     copy_opts.add_argument(
         '--project-uuid', dest='project_uuid',
         help='The UUID of the project at the destination to which the pipeline should be copied.')
+    copy_opts.add_argument(
+        '--allow-git-http-src', action="store_true",
+        help='Allow cloning git repositories over insecure http')
+    copy_opts.add_argument(
+        '--allow-git-http-dst', action="store_true",
+        help='Allow pushing git repositories over insecure http')
+
     copy_opts.add_argument(
         'object_uuid',
         help='The UUID of the object to be copied.')
@@ -583,6 +591,55 @@ def copy_collection(obj_uuid, src, dst, args):
     c['manifest_text'] = dst_manifest
     return create_collection_from(c, src, dst, args)
 
+def select_git_url(api, repo_name, retries, allow_insecure_http, allow_insecure_http_opt):
+    r = api.repositories().list(
+        filters=[['name', '=', repo_name]]).execute(num_retries=retries)
+    if r['items_available'] != 1:
+        raise Exception('cannot identify repo {}; {} repos found'
+                        .format(repo_name, r['items_available']))
+
+    https_url = [c for c in r['items'][0]["clone_urls"] if c.startswith("https:")]
+    http_url = [c for c in r['items'][0]["clone_urls"] if c.startswith("http:")]
+    other_url = [c for c in r['items'][0]["clone_urls"] if not c.startswith("http")]
+
+    priority = https_url + other_url + http_url
+
+    git_config = []
+    git_url = None
+    for url in priority:
+        if url.startswith("http"):
+            u = urlparse.urlsplit(url)
+            baseurl = urlparse.urlunsplit((u.scheme, u.netloc, "", "", ""))
+            git_config = ["-c", "credential.%s/.username=none" % baseurl,
+                          "-c", "credential.%s/.helper=!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred" % baseurl]
+        else:
+            git_config = []
+
+        try:
+            logger.debug("trying %s", url)
+            arvados.util.run_command(["git"] + git_config + ["ls-remote", url],
+                                      env={"HOME": os.environ["HOME"],
+                                           "ARVADOS_API_TOKEN": api.api_token,
+                                           "GIT_ASKPASS": "/bin/false"})
+        except arvados.errors.CommandFailedError:
+            pass
+        else:
+            git_url = url
+            break
+
+    if not git_url:
+        raise Exception('Cannot access git repository, tried {}'
+                        .format(priority))
+
+    if git_url.startswith("http:"):
+        if allow_insecure_http:
+            logger.warn("Using insecure git url %s but will allow this because %s", git_url, allow_insecure_http_opt)
+        else:
+            raise Exception("Refusing to use insecure git url %s, use %s if you really want this." % (git_url, allow_insecure_http_opt))
+
+    return (git_url, git_config)
+
+
 # copy_git_repo(src_git_repo, src, dst, dst_git_repo, script_version, args)
 #
 #    Copies commits from git repository 'src_git_repo' on Arvados
@@ -600,21 +657,12 @@ def copy_collection(obj_uuid, src, dst, args):
 #
 def copy_git_repo(src_git_repo, src, dst, dst_git_repo, script_version, args):
     # Identify the fetch and push URLs for the git repositories.
-    r = src.repositories().list(
-        filters=[['name', '=', src_git_repo]]).execute(num_retries=args.retries)
-    if r['items_available'] != 1:
-        raise Exception('cannot identify source repo {}; {} repos found'
-                        .format(src_git_repo, r['items_available']))
-    src_git_url = r['items'][0]['fetch_url']
-    logger.debug('src_git_url: {}'.format(src_git_url))
 
-    r = dst.repositories().list(
-        filters=[['name', '=', dst_git_repo]]).execute(num_retries=args.retries)
-    if r['items_available'] != 1:
-        raise Exception('cannot identify destination repo {}; {} repos found'
-                        .format(dst_git_repo, r['items_available']))
-    dst_git_push_url  = r['items'][0]['push_url']
-    logger.debug('dst_git_push_url: {}'.format(dst_git_push_url))
+    (src_git_url, src_git_config) = select_git_url(src, src_git_repo, args.retries, args.allow_git_http_src, "--allow-git-http-src")
+    (dst_git_url, dst_git_config) = select_git_url(dst, dst_git_repo, args.retries, args.allow_git_http_dst, "--allow-git-http-dst")
+
+    logger.debug('src_git_url: {}'.format(src_git_url))
+    logger.debug('dst_git_url: {}'.format(dst_git_url))
 
     dst_branch = re.sub(r'\W+', '_', "{}_{}".format(src_git_url, script_version))
 
@@ -622,17 +670,23 @@ def copy_git_repo(src_git_repo, src, dst, dst_git_repo, script_version, args):
     if src_git_repo not in local_repo_dir:
         local_repo_dir[src_git_repo] = tempfile.mkdtemp()
         arvados.util.run_command(
-            ["git""clone", "--bare", src_git_url,
+            ["git"] + src_git_config + ["clone", "--bare", src_git_url,
              local_repo_dir[src_git_repo]],
-            cwd=os.path.dirname(local_repo_dir[src_git_repo]))
+            cwd=os.path.dirname(local_repo_dir[src_git_repo]),
+            env={"HOME": os.environ["HOME"],
+                 "ARVADOS_API_TOKEN": src.api_token,
+                 "GIT_ASKPASS": "/bin/false"})
         arvados.util.run_command(
-            ["git", "remote", "add", "dst", dst_git_push_url],
+            ["git", "remote", "add", "dst", dst_git_url],
             cwd=local_repo_dir[src_git_repo])
     arvados.util.run_command(
         ["git", "branch", dst_branch, script_version],
         cwd=local_repo_dir[src_git_repo])
-    arvados.util.run_command(["git", "push", "dst", dst_branch],
-                             cwd=local_repo_dir[src_git_repo])
+    arvados.util.run_command(["git"] + dst_git_config + ["push", "dst", dst_branch],
+                             cwd=local_repo_dir[src_git_repo],
+                             env={"HOME": os.environ["HOME"],
+                                  "ARVADOS_API_TOKEN": dst.api_token,
+                                  "GIT_ASKPASS": "/bin/false"})
 
 def copy_docker_images(pipeline, src, dst, args):
     """Copy any docker images named in the pipeline components'
index b6518f95a17495851c72985af0e8b16a97e3f1b6..759e8ff67edf1ec8b99b0de86ee8a3e4602b73b7 100644 (file)
@@ -39,8 +39,9 @@ setup(name='arvados-python-client',
           ('share/doc/arvados-python-client', ['LICENSE-2.0.txt', 'README.rst']),
       ],
       install_requires=[
+          'google-api-python-client==1.4.2',
+          'oauth2client >=1.4.6, <2',
           'ciso8601',
-          'google-api-python-client',
           'httplib2',
           'pycurl >=7.19.5.1, <7.21.5',
           'python-gflags<3.0',
index 3adcf4d2c169ed047ef8d138829edb163c37ff9f..3d090f4b5cc69aa23eec3dc057041c200024ed9a 100644 (file)
@@ -19,10 +19,11 @@ Gem::Specification.new do |s|
                    "lib/arvados/collection.rb", "lib/arvados/keep.rb",
                    "README", "LICENSE-2.0.txt"]
   s.required_ruby_version = '>= 2.1.0'
+  # activesupport <4.2.6 only because https://dev.arvados.org/issues/8222
+  s.add_dependency('activesupport', '>= 3.2.13', '< 4.2.6')
+  s.add_dependency('andand', '~> 1.3', '>= 1.3.3')
   s.add_dependency('google-api-client', '~> 0.6.3', '>= 0.6.3')
-  s.add_dependency('activesupport', '>= 3.2.13')
   s.add_dependency('json', '~> 1.7', '>= 1.7.7')
-  s.add_dependency('andand', '~> 1.3', '>= 1.3.3')
   s.add_runtime_dependency('jwt', '>= 0.1.5', '< 1.0.0')
   s.homepage    =
     'https://arvados.org'
index 2eb79c090dcd69a4e6e4d4157b0ea0f0d2de5afc..56d0d85a82b51b1c0b6e2af981f8053c267ebd88 100644 (file)
@@ -49,19 +49,25 @@ class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
   def find_objects_for_index
     # Here we are deliberately less helpful about searching for client
     # authorizations.  We look up tokens belonging to the current user
-    # and filter by exact matches on api_token and scopes.
+    # and filter by exact matches on uuid, api_token, and scopes.
     wanted_scopes = []
     if @filters
       wanted_scopes.concat(@filters.map { |attr, operator, operand|
         ((attr == 'scopes') and (operator == '=')) ? operand : nil
       })
       @filters.select! { |attr, operator, operand|
-        ((attr == 'uuid') and (operator == '=')) || ((attr == 'api_token') and (operator == '='))
+        operator == '=' && (attr == 'uuid' || attr == 'api_token')
       }
     end
     if @where
       wanted_scopes << @where['scopes']
-      @where.select! { |attr, val| attr == 'uuid' }
+      @where.select! { |attr, val|
+        # "where":{"uuid":"zzzzz-zzzzz-zzzzzzzzzzzzzzz"} is OK but
+        # "where":{"api_client_id":1} is not supported
+        # "where":{"uuid":["contains","-"]} is not supported
+        # "where":{"uuid":["uuid1","uuid2","uuid3"]} is not supported
+        val.is_a?(String) && (attr == 'uuid' || attr == 'api_token')
+      }
     end
     @objects = model_class.
       includes(:user, :api_client).
@@ -74,25 +80,46 @@ class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
   end
 
   def find_object_by_uuid
-    @object = model_class.where(uuid: (params[:uuid] || params[:id])).first
+    uuid_param = params[:uuid] || params[:id]
+    if (uuid_param != current_api_client_authorization.andand.uuid and
+        not Thread.current[:api_client].andand.is_trusted)
+      return forbidden
+    end
+    @limit = 1
+    @offset = 0
+    @orders = []
+    @where = {}
+    @filters = [['uuid', '=', uuid_param]]
+    find_objects_for_index
+    @object = @objects.first
   end
 
   def current_api_client_is_trusted
-    unless Thread.current[:api_client].andand.is_trusted
-      if params["action"] == "show"
-        if @object and @object['api_token'] == current_api_client_authorization.andand.api_token
-          return true
-        end
-      elsif params["action"] == "index" and @objects.andand.size == 1
-        filters = @filters.map{|f|f.first}.uniq
-        if ['uuid'] == filters
-          return true if @objects.first['api_token'] == current_api_client_authorization.andand.api_token
-        elsif ['api_token'] == filters
-          return true if @objects.first[:user_id] = current_user.id
-        end
-      end
-      send_error('Forbidden: this API client cannot manipulate other clients\' access tokens.',
-                 status: 403)
+    if Thread.current[:api_client].andand.is_trusted
+      return true
+    end
+    # A non-trusted client can do a search for its own token if it
+    # explicitly restricts the search to its own UUID or api_token.
+    # Any other kind of query must return 403, even if it matches only
+    # the current token, because that's currently how Workbench knows
+    # (after searching on scopes) the difference between "the token
+    # I'm using now *is* the only sharing token for this collection"
+    # (403) and "my token is trusted, and there is one sharing token
+    # for this collection" (200).
+    #
+    # The @filters test here also prevents a non-trusted token from
+    # filtering on its own scopes, and discovering whether any _other_
+    # equally scoped tokens exist (403=yes, 200=no).
+    if (@objects.andand.count == 1 and
+        @objects.first.uuid == current_api_client_authorization.andand.uuid and
+        (@filters.map(&:first) & %w(uuid api_token)).any?)
+      return true
     end
+    forbidden
+  end
+
+  def forbidden
+    send_error('Forbidden: this API client cannot manipulate other clients\' access tokens.',
+               status: 403)
   end
 end
index 4bf9a6a0945462e2bf74596d620ec21575541844..183ed4d8a80e269356c82cdf19b08d5dc0120a80 100644 (file)
@@ -91,11 +91,11 @@ class Arvados::V1::RepositoriesController < ApplicationController
     @repo_info.values.each do |repo|
       repo[:user_permissions].each do |user_uuid, user_perms|
         if user_perms['can_manage']
-          user_perms['gitolite_permissions'] = 'RW'
+          user_perms['gitolite_permissions'] = 'RW+'
           user_perms['can_write'] = true
           user_perms['can_read'] = true
         elsif user_perms['can_write']
-          user_perms['gitolite_permissions'] = 'RW'
+          user_perms['gitolite_permissions'] = 'RW+'
           user_perms['can_read'] = true
         elsif user_perms['can_read']
           user_perms['gitolite_permissions'] = 'R'
index c587e5830af41549c5bd637c7ffa9472bbf51017..499a61b7d3e93116b50f3e96beffbe846466c676 100644 (file)
@@ -82,8 +82,9 @@ class ApiClientAuthorization < ArvadosModel
 
   def permission_to_update
     (permission_to_create and
-     not self.user_id_changed? and
-     not self.owner_uuid_changed?)
+     not uuid_changed? and
+     not user_id_changed? and
+     not owner_uuid_changed?)
   end
 
   def log_update
index 40c9ef6dfc742d5cb5a4256183ed29aff4b8bb30..66916836c23fb9538281400ad84b1f1f672247e2 100644 (file)
@@ -338,6 +338,7 @@ common:
   # "git log".
   source_version: false
 
+  crunch_log_partial_line_throttle_period: 5
 
 development:
   force_ssl: false
index b59279e554643c5015fb371ec3774e34bc28f99f..ce94f737a2467f855a7156ba76873db57cd183ee 100644 (file)
@@ -24,6 +24,7 @@ class CrunchDispatch
 
     @docker_bin = ENV['CRUNCH_JOB_DOCKER_BIN']
     @docker_run_args = ENV['CRUNCH_JOB_DOCKER_RUN_ARGS']
+    @cgroup_root = ENV['CRUNCH_CGROUP_ROOT']
 
     @arvados_internal = Rails.configuration.git_internal_dir
     if not File.exists? @arvados_internal
@@ -384,6 +385,10 @@ class CrunchDispatch
                    '--job', job.uuid,
                    '--git-dir', @arvados_internal]
 
+      if @cgroup_root
+        cmd_args += ['--cgroup-root', @cgroup_root]
+      end
+
       if @docker_bin
         cmd_args += ['--docker-bin', @docker_bin]
       end
@@ -429,6 +434,8 @@ class CrunchDispatch
         log_throttle_bytes_so_far: 0,
         log_throttle_lines_so_far: 0,
         log_throttle_bytes_skipped: 0,
+        log_throttle_partial_line_last_at: Time.new(0),
+        log_throttle_first_partial_line: true,
       }
       i.close
       @todo_job_retries.delete(job.uuid)
@@ -443,9 +450,23 @@ class CrunchDispatch
     message = false
     linesize = line.size
     if running_job[:log_throttle_is_open]
-      running_job[:log_throttle_lines_so_far] += 1
-      running_job[:log_throttle_bytes_so_far] += linesize
-      running_job[:bytes_logged] += linesize
+      partial_line = false
+      skip_counts = false
+      matches = line.match(/^\S+ \S+ \d+ \d+ stderr (.*)/)
+      if matches and matches[1] and matches[1].start_with?('[...]') and matches[1].end_with?('[...]')
+        partial_line = true
+        if Time.now > running_job[:log_throttle_partial_line_last_at] + Rails.configuration.crunch_log_partial_line_throttle_period
+          running_job[:log_throttle_partial_line_last_at] = Time.now
+        else
+          skip_counts = true
+        end
+      end
+
+      if !skip_counts
+        running_job[:log_throttle_lines_so_far] += 1
+        running_job[:log_throttle_bytes_so_far] += linesize
+        running_job[:bytes_logged] += linesize
+      end
 
       if (running_job[:bytes_logged] >
           Rails.configuration.crunch_limit_log_bytes_per_job)
@@ -456,14 +477,18 @@ class CrunchDispatch
       elsif (running_job[:log_throttle_bytes_so_far] >
              Rails.configuration.crunch_log_throttle_bytes)
         remaining_time = running_job[:log_throttle_reset_time] - Time.now
-        message = "Exceeded rate #{Rails.configuration.crunch_log_throttle_bytes} bytes per #{Rails.configuration.crunch_log_throttle_period} seconds (crunch_log_throttle_bytes). Logging will be silenced for the next #{remaining_time.round} seconds.\n"
+        message = "Exceeded rate #{Rails.configuration.crunch_log_throttle_bytes} bytes per #{Rails.configuration.crunch_log_throttle_period} seconds (crunch_log_throttle_bytes). Logging will be silenced for the next #{remaining_time.round} seconds."
         running_job[:log_throttle_is_open] = false
 
       elsif (running_job[:log_throttle_lines_so_far] >
              Rails.configuration.crunch_log_throttle_lines)
         remaining_time = running_job[:log_throttle_reset_time] - Time.now
-        message = "Exceeded rate #{Rails.configuration.crunch_log_throttle_lines} lines per #{Rails.configuration.crunch_log_throttle_period} seconds (crunch_log_throttle_lines), logging will be silenced for the next #{remaining_time.round} seconds.\n"
+        message = "Exceeded rate #{Rails.configuration.crunch_log_throttle_lines} lines per #{Rails.configuration.crunch_log_throttle_period} seconds (crunch_log_throttle_lines), logging will be silenced for the next #{remaining_time.round} seconds."
         running_job[:log_throttle_is_open] = false
+
+      elsif partial_line and running_job[:log_throttle_first_partial_line]
+        running_job[:log_throttle_first_partial_line] = false
+        message = "Rate-limiting partial segments of long lines to one every #{Rails.configuration.crunch_log_partial_line_throttle_period} seconds."
       end
     end
 
@@ -475,8 +500,11 @@ class CrunchDispatch
     if message
       # Yes, write to logs, but use our "rate exceeded" message
       # instead of the log message that exceeded the limit.
+      message += " A complete log is still being written to Keep, and will be available when the job finishes.\n"
       line.replace message
       true
+    elsif partial_line
+      false
     else
       running_job[:log_throttle_is_open]
     end
@@ -501,6 +529,8 @@ class CrunchDispatch
         j[:log_throttle_lines_so_far] = 0
         j[:log_throttle_bytes_skipped] = 0
         j[:log_throttle_is_open] = true
+        j[:log_throttle_partial_line_last_at] = Time.new(0)
+        j[:log_throttle_first_partial_line] = true
       end
 
       j[:buf].each do |stream, streambuf|
index 556a5c047120923aa557a5fe38eab002313aaad3..b1154a8399e478d9fd2147fbb5fdb6cc8d46e016 100644 (file)
@@ -244,6 +244,7 @@ real_log_collection:
   uuid: zzzzz-4zz18-op4e2lbej01tcvu
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   created_at: 2014-09-01 12:00:00
+  modified_at: 2014-09-01 12:00:00
   portable_data_hash: 0b9a7787660e1fce4a93f33e01376ba6+81
   manifest_text: ". cdd549ae79fe6640fa3d5c6261d8303c+195 0:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt\n"
   name: real_log_collection
@@ -278,6 +279,7 @@ collection_owned_by_foo:
   manifest_text: ". 73feffa4b7f6bb68e44cf984c85f6e88+3 0:3:baz\n"
   owner_uuid: zzzzz-tpzed-81hsbo6mk8nl05c
   created_at: 2014-02-03T17:22:54Z
+  modified_at: 2014-02-03T17:22:54Z
   name: collection_owned_by_foo
 
 collection_to_remove_from_subproject:
@@ -287,6 +289,7 @@ collection_to_remove_from_subproject:
   manifest_text: ". 8258b505536a9ab47baa2f4281cb932a+9 0:9:missingno\n"
   owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
   created_at: 2014-10-15T10:45:00
+  modified_at: 2014-10-15T10:45:00
   name: Collection to remove from subproject
 
 collection_with_files_in_subdir:
@@ -307,6 +310,8 @@ graph_test_collection1:
   portable_data_hash: fa7aeb5140e2848d39b416daeef4ffc5+45
   manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
   name: bar_file
+  created_at: 2014-02-03T17:22:54Z
+  modified_at: 2014-02-03T17:22:54Z
 
 graph_test_collection2:
   uuid: zzzzz-4zz18-uukreo9rbgwsujx
@@ -314,6 +319,8 @@ graph_test_collection2:
   portable_data_hash: 65b17c95fdbc9800fc48acda4e9dcd0b+93
   manifest_text: ". 6a4ff0499484c6c79c95cd8c566bd25f+249025 0:249025:FOO_General_Public_License,_version_3.pdf\n"
   name: "FOO General Public License, version 3"
+  created_at: 2014-02-03T17:22:54Z
+  modified_at: 2014-02-03T17:22:54Z
 
 graph_test_collection3:
   uuid: zzzzz-4zz18-uukreo9rbgwsujj
@@ -321,6 +328,8 @@ graph_test_collection3:
   portable_data_hash: ea10d51bcf88862dbcc36eb292017dfd+45
   manifest_text: ". 73feffa4b7f6bb68e44cf984c85f6e88+3 0:3:baz\n"
   name: "baz file"
+  created_at: 2014-02-03T17:22:54Z
+  modified_at: 2014-02-03T17:22:54Z
 
 collection_1_owned_by_fuse:
   uuid: zzzzz-4zz18-ovx05bfzormx3bg
@@ -557,6 +566,7 @@ collection_<%=i%>_of_10:
   uuid: zzzzz-4zz18-10gneyn6brkx<%= i.to_s.rjust(3, '0') %>
   owner_uuid: zzzzz-j7d0g-0010collections
   created_at: <%= i.minute.ago.to_s(:db) %>
+  modified_at: <%= i.minute.ago.to_s(:db) %>
 <% end %>
 
 # collections in project_with_201_collections
@@ -568,6 +578,7 @@ collection_<%=i%>_of_201:
   uuid: zzzzz-4zz18-201gneyn6brd<%= i.to_s.rjust(3, '0') %>
   owner_uuid: zzzzz-j7d0g-0201collections
   created_at: <%= i.minute.ago.to_s(:db) %>
+  modified_at: <%= i.minute.ago.to_s(:db) %>
 <% end %>
 
 # Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
index 5da9145a81e052b3ef5a471f672c7568399e428b..192e6b956dad89bb7e70dea714800a986f9574ab 100644 (file)
@@ -68,46 +68,80 @@ class Arvados::V1::ApiClientAuthorizationsControllerTest < ActionController::Tes
     end
   end
 
-  [
-    [:admin, :admin, 200],
-    [:admin, :active, 403],
-    [:admin, :admin_vm, 403], # this belongs to the user of current session, but we can't get it by uuid
-    [:admin_trustedclient, :active, 200],
-  ].each do |user, token, status|
-    test "as user #{user} get #{token} token and expect #{status}" do
+  [# anyone can look up the token they're currently using
+   [:admin, :admin, 200, 200, 1],
+   [:active, :active, 200, 200, 1],
+   # cannot look up other tokens (even for same user) if not trustedclient
+   [:admin, :active, 403, 403],
+   [:admin, :admin_vm, 403, 403],
+   [:active, :admin, 403, 403],
+   # cannot look up other tokens for other users, regardless of trustedclient
+   [:admin_trustedclient, :active, 404, 200, 0],
+   [:active_trustedclient, :admin, 404, 200, 0],
+  ].each do |user, token, expect_get_response, expect_list_response, expect_list_items|
+    test "using '#{user}', get '#{token}' by uuid" do
       authorize_with user
-      get :show, {id: api_client_authorizations(token).uuid}
-      assert_response status
+      get :show, {
+        id: api_client_authorizations(token).uuid,
+      }
+      assert_response expect_get_response
+    end
+
+    test "using '#{user}', update '#{token}' by uuid" do
+      authorize_with user
+      put :update, {
+        id: api_client_authorizations(token).uuid,
+        api_client_authorization: {},
+      }
+      assert_response expect_get_response
+    end
+
+    test "using '#{user}', delete '#{token}' by uuid" do
+      authorize_with user
+      post :destroy, {
+        id: api_client_authorizations(token).uuid,
+      }
+      assert_response expect_get_response
     end
-  end
 
-  [
-    [:admin, :admin, 200],
-    [:admin, :active, 403],
-    [:admin, :admin_vm, 403], # this belongs to the user of current session, but we can't list it by uuid
-    [:admin_trustedclient, :active, 200],
-  ].each do |user, token, status|
-    test "as user #{user} list #{token} token using uuid and expect #{status}" do
+    test "using '#{user}', list '#{token}' by uuid" do
       authorize_with user
       get :index, {
-        filters: [['uuid','=',api_client_authorizations(token).uuid]]
+        filters: [['uuid','=',api_client_authorizations(token).uuid]],
       }
-      assert_response status
+      assert_response expect_list_response
+      if expect_list_items
+        assert_equal assigns(:objects).length, expect_list_items
+      end
     end
-  end
 
-  [
-    [:admin, :admin, 200],
-    [:admin, :active, 403],
-    [:admin, :admin_vm, 200], # this belongs to the user of current session, and can be listed by token
-    [:admin_trustedclient, :active, 200],
-  ].each do |user, token, status|
-    test "as user #{user} list #{token} token using token and expect #{status}" do
+    test "using '#{user}', list '#{token}' by token" do
       authorize_with user
       get :index, {
-        filters: [['api_token','=',api_client_authorizations(token).api_token]]
+        filters: [['api_token','=',api_client_authorizations(token).api_token]],
       }
-      assert_response status
+      assert_response expect_list_response
+      if expect_list_items
+        assert_equal assigns(:objects).length, expect_list_items
+      end
     end
   end
+
+  test "scoped token cannot change its own scopes" do
+    authorize_with :admin_vm
+    put :update, {
+      id: api_client_authorizations(:admin_vm).uuid,
+      api_client_authorization: {scopes: ['all']},
+    }
+    assert_response 403
+  end
+
+  test "token cannot change its own uuid" do
+    authorize_with :admin
+    put :update, {
+      id: api_client_authorizations(:admin).uuid,
+      api_client_authorization: {uuid: 'zzzzz-gj3su-zzzzzzzzzzzzzzz'},
+    }
+    assert_response 403
+  end
 end
index 514bb66bb2b55eaabfffd9e2494c59500c1a58bc..71b528e72afd9467539f2136d3163481e582b956 100644 (file)
@@ -176,13 +176,13 @@ class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
         end
         if perms['can_write']
           assert u.can? write: repo['uuid']
-          assert_match /RW/, perms['gitolite_permissions']
+          assert_match /RW\+/, perms['gitolite_permissions']
         else
           refute_match /W/, perms['gitolite_permissions']
         end
         if perms['can_manage']
           assert u.can? manage: repo['uuid']
-          assert_match /RW/, perms['gitolite_permissions']
+          assert_match /RW\+/, perms['gitolite_permissions']
         end
       end
     end
@@ -197,10 +197,10 @@ class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
   end
 
   [
-    {cfg: :git_repo_ssh_base, cfgval: "git@example.com:", match: %r"^git@example.com:/"},
-    {cfg: :git_repo_ssh_base, cfgval: true, match: %r"^git@git.zzzzz.arvadosapi.com:/"},
+    {cfg: :git_repo_ssh_base, cfgval: "git@example.com:", match: %r"^git@example.com:"},
+    {cfg: :git_repo_ssh_base, cfgval: true, match: %r"^git@git.zzzzz.arvadosapi.com:"},
     {cfg: :git_repo_ssh_base, cfgval: false, refute: /^git@/ },
-    {cfg: :git_repo_https_base, cfgval: "https://example.com/", match: %r"https://example.com/"},
+    {cfg: :git_repo_https_base, cfgval: "https://example.com/", match: %r"^https://example.com/"},
     {cfg: :git_repo_https_base, cfgval: true, match: %r"^https://git.zzzzz.arvadosapi.com/"},
     {cfg: :git_repo_https_base, cfgval: false, refute: /^http/ },
   ].each do |expect|
@@ -209,15 +209,17 @@ class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
       authorize_with :active
       get :index
       assert_response :success
+      assert_not_empty json_response['items']
       json_response['items'].each do |r|
         if expect[:refute]
           r['clone_urls'].each do |u|
             refute_match expect[:refute], u
           end
         else
-          assert r['clone_urls'].any? do |u|
-            expect[:prefix].match u
-          end
+          assert((r['clone_urls'].any? do |u|
+                    expect[:match].match u
+                  end),
+                 "no match for #{expect[:match]} in #{r['clone_urls'].inspect}")
         end
       end
     end
index 09f4af6373fa03a2f4bdb79e332ea7b38130ff0a..b7f0a96376e446bde2230dc35ba9fb8486a4916f 100644 (file)
@@ -1,7 +1,10 @@
 require 'test_helper'
 require 'crunch_dispatch'
+require 'helpers/git_test_helper'
 
 class CrunchDispatchTest < ActiveSupport::TestCase
+  include GitTestHelper
+
   test 'choose cheaper nodes first' do
     act_as_system_user do
       # Replace test fixtures with a set suitable for testing dispatch
@@ -100,6 +103,30 @@ class CrunchDispatchTest < ActiveSupport::TestCase
     end
   end
 
+  test 'override --cgroup-root with CRUNCH_CGROUP_ROOT' do
+    ENV['CRUNCH_CGROUP_ROOT'] = '/path/to/cgroup'
+    Rails.configuration.crunch_job_wrapper = :none
+    act_as_system_user do
+      j = Job.create(repository: 'active/foo',
+                     script: 'hash',
+                     script_version: '4fe459abe02d9b365932b8f5dc419439ab4e2577',
+                     script_parameters: {})
+      ok = false
+      Open3.expects(:popen3).at_least_once.with do |*args|
+        if args.index(j.uuid)
+          ok = ((i = args.index '--cgroup-root') and
+                (args[i+1] == '/path/to/cgroup'))
+        end
+        true
+      end.raises(StandardError.new('all is well'))
+      dispatch = CrunchDispatch.new
+      dispatch.parse_argv ['--jobs']
+      dispatch.refresh_todo
+      dispatch.start_jobs
+      assert ok
+    end
+  end
+
   def assert_with_timeout timeout, message
     t = 0
     while (t += 0.1) < timeout
@@ -116,4 +143,62 @@ class CrunchDispatchTest < ActiveSupport::TestCase
       return f.flock(File::LOCK_EX|File::LOCK_NB)
     end
   end
+
+  test 'rate limit of partial line segments' do
+    act_as_system_user do
+      Rails.configuration.crunch_log_partial_line_throttle_period = 1
+
+      job = {}
+      job[:bytes_logged] = 0
+      job[:log_throttle_bytes_so_far] = 0
+      job[:log_throttle_lines_so_far] = 0
+      job[:log_throttle_bytes_skipped] = 0
+      job[:log_throttle_is_open] = true
+      job[:log_throttle_partial_line_last_at] = Time.new(0)
+      job[:log_throttle_first_partial_line] = true
+
+      dispatch = CrunchDispatch.new
+
+      line = "first log line"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal true, limit
+      assert_equal "first log line", line
+      assert_equal 1, job[:log_throttle_lines_so_far]
+
+      # first partial line segment is skipped and counted towards skipped lines
+      now = Time.now.strftime('%Y-%m-%d-%H:%M:%S')
+      line = "#{now} localhost 100 0 stderr [...] this is first partial line segment [...]"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal true, limit
+      assert_includes line, "Rate-limiting partial segments of long lines", line
+      assert_equal 2, job[:log_throttle_lines_so_far]
+
+      # next partial line segment within throttle interval is skipped but not counted towards skipped lines
+      line = "#{now} localhost 100 0 stderr [...] second partial line segment within the interval [...]"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal false, limit
+      assert_equal 2, job[:log_throttle_lines_so_far]
+
+      # next partial line after interval is counted towards skipped lines
+      sleep(1)
+      line = "#{now} localhost 100 0 stderr [...] third partial line segment after the interval [...]"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal false, limit
+      assert_equal 3, job[:log_throttle_lines_so_far]
+
+      # this is not a valid line segment
+      line = "#{now} localhost 100 0 stderr [...] does not end with [...] and is not a partial segment"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal true, limit
+      assert_equal "#{now} localhost 100 0 stderr [...] does not end with [...] and is not a partial segment", line
+      assert_equal 4, job[:log_throttle_lines_so_far]
+
+      # this also is not a valid line segment
+      line = "#{now} localhost 100 0 stderr does not start correctly but ends with [...]"
+      limit = dispatch.rate_limit(job, line)
+      assert_equal true, limit
+      assert_equal "#{now} localhost 100 0 stderr does not start correctly but ends with [...]", line
+      assert_equal 5, job[:log_throttle_lines_so_far]
+    end
+  end
 end
index aa710262b499b9c0a5e5168a8a69fc039ddeca36..20bdae7ec13a5534ebd4f69248869d4980688fa7 100644 (file)
@@ -1,9 +1,10 @@
 package main
 
 import (
+       "io/ioutil"
        "os"
        "os/exec"
-       "io/ioutil"
+       "strings"
 
        check "gopkg.in/check.v1"
 )
@@ -26,7 +27,12 @@ func (s *GitoliteSuite) SetUpTest(c *check.C) {
                c.Log(prog, " ", args)
                cmd := exec.Command(prog, args...)
                cmd.Dir = s.gitoliteHome
-               cmd.Env = append(os.Environ(), "HOME=" + s.gitoliteHome)
+               cmd.Env = []string{"HOME=" + s.gitoliteHome}
+               for _, e := range os.Environ() {
+                       if !strings.HasPrefix(e, "HOME=") {
+                               cmd.Env = append(cmd.Env, e)
+                       }
+               }
                diags, err := cmd.CombinedOutput()
                c.Log(string(diags))
                c.Assert(err, check.Equals, nil)
@@ -76,13 +82,13 @@ func (s *GitoliteSuite) TestPush(c *check.C) {
        // Check that the commit hash appears in the gitolite log, as
        // assurance that the gitolite hooks really did run.
 
-       sha1, err := exec.Command("git", "--git-dir", s.tmpWorkdir + "/.git",
+       sha1, err := exec.Command("git", "--git-dir", s.tmpWorkdir+"/.git",
                "log", "-n1", "--format=%H").CombinedOutput()
        c.Logf("git-log in workdir: %q", string(sha1))
        c.Assert(err, check.Equals, nil)
        c.Assert(len(sha1), check.Equals, 41)
 
-       gitoliteLog, err := exec.Command("grep", "-r", string(sha1[:40]), s.gitoliteHome + "/.gitolite/logs").CombinedOutput()
+       gitoliteLog, err := exec.Command("grep", "-r", string(sha1[:40]), s.gitoliteHome+"/.gitolite/logs").CombinedOutput()
        c.Check(err, check.Equals, nil)
        c.Logf("gitolite log message: %q", string(gitoliteLog))
 }
index 039a649a6dc17c13f5cfc53d81c7139224876bbe..01edb0a516fadd33c175df030c7c4c330b2985ba 100644 (file)
@@ -120,7 +120,7 @@ type ContainerRunner struct {
 
 // SetupSignals sets up signal handling to gracefully terminate the underlying
 // Docker container and update state when receiving a TERM, INT or QUIT signal.
-func (runner *ContainerRunner) SetupSignals() error {
+func (runner *ContainerRunner) SetupSignals() {
        runner.SigChan = make(chan os.Signal, 1)
        signal.Notify(runner.SigChan, syscall.SIGTERM)
        signal.Notify(runner.SigChan, syscall.SIGINT)
@@ -138,8 +138,6 @@ func (runner *ContainerRunner) SetupSignals() error {
                        }
                }
        }(runner.SigChan)
-
-       return nil
 }
 
 // LoadImage determines the docker image id from the container record and
@@ -355,12 +353,12 @@ func (runner *ContainerRunner) ProcessDockerAttach(containerReader io.Reader) {
 
                        closeerr := runner.Stdout.Close()
                        if closeerr != nil {
-                               runner.CrunchLog.Printf("While closing stdout logs: %v", readerr)
+                               runner.CrunchLog.Printf("While closing stdout logs: %v", closeerr)
                        }
 
                        closeerr = runner.Stderr.Close()
                        if closeerr != nil {
-                               runner.CrunchLog.Printf("While closing stderr logs: %v", readerr)
+                               runner.CrunchLog.Printf("While closing stderr logs: %v", closeerr)
                        }
 
                        runner.loggingDone <- true
@@ -380,7 +378,7 @@ func (runner *ContainerRunner) AttachStreams() (err error) {
        containerReader, err = runner.Docker.AttachContainer(runner.ContainerID,
                &dockerclient.AttachOptions{Stream: true, Stdout: true, Stderr: true})
        if err != nil {
-               return fmt.Errorf("While attaching container logs: %v", err)
+               return fmt.Errorf("While attaching container stdout/stderr streams: %v", err)
        }
 
        runner.loggingDone = make(chan bool)
@@ -419,9 +417,8 @@ func (runner *ContainerRunner) StartContainer() (err error) {
        hostConfig := &dockerclient.HostConfig{Binds: runner.Binds,
                LogConfig: dockerclient.LogConfig{Type: "none"}}
 
-       runner.AttachStreams()
+       err = runner.AttachStreams()
        if err != nil {
-               return fmt.Errorf("While attaching streams: %v", err)
                return err
        }
 
@@ -647,7 +644,7 @@ func (runner *ContainerRunner) Run() (err error) {
                        if runerr != nil {
                                err = runerr
                        } else if waiterr != nil {
-                               err = runerr
+                               err = waiterr
                        } else if logerr != nil {
                                err = logerr
                        } else if updateerr != nil {
@@ -662,10 +659,7 @@ func (runner *ContainerRunner) Run() (err error) {
        }
 
        // (1) setup signal handling
-       err = runner.SetupSignals()
-       if err != nil {
-               return fmt.Errorf("While setting up signal handling: %v", err)
-       }
+       runner.SetupSignals()
 
        // (2) check for and/or load image
        err = runner.LoadImage()
index bf75e7f0eab2e539f97744f2315b9130ff4fa48f..659b3c0ede524a31af3ada93369fcc6cab808e2a 100644 (file)
@@ -36,7 +36,7 @@ var _ = Suite(&TestSuite{})
 type ArvTestClient struct {
        Total   int64
        Calls   int
-       Content arvadosclient.Dict
+       Content []arvadosclient.Dict
        ContainerRecord
        Logs          map[string]*bytes.Buffer
        WasSetRunning bool
@@ -131,7 +131,7 @@ func (this *ArvTestClient) Create(resourceType string,
        output interface{}) error {
 
        this.Calls += 1
-       this.Content = parameters
+       this.Content = append(this.Content, parameters)
 
        if resourceType == "logs" {
                et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
@@ -168,8 +168,8 @@ func (this *ArvTestClient) Get(resourceType string, uuid string, parameters arva
 }
 
 func (this *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
-
-       this.Content = parameters
+       this.Calls += 1
+       this.Content = append(this.Content, parameters)
        if resourceType == "containers" {
                if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
                        this.WasSetRunning = true
@@ -399,8 +399,9 @@ func (s *TestSuite) TestCommitLogs(c *C) {
        err := cr.CommitLogs()
        c.Check(err, IsNil)
 
-       c.Check(api.Content["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
-       c.Check(api.Content["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
+       c.Check(api.Calls, Equals, 2)
+       c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
+       c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
        c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
 }
 
@@ -412,7 +413,7 @@ func (s *TestSuite) TestUpdateContainerRecordRunning(c *C) {
        err := cr.UpdateContainerRecordRunning()
        c.Check(err, IsNil)
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Running")
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Running")
 }
 
 func (s *TestSuite) TestUpdateContainerRecordComplete(c *C) {
@@ -430,9 +431,9 @@ func (s *TestSuite) TestUpdateContainerRecordComplete(c *C) {
        err := cr.UpdateContainerRecordComplete()
        c.Check(err, IsNil)
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 }
 
 func (s *TestSuite) TestUpdateContainerRecordCancelled(c *C) {
@@ -445,9 +446,9 @@ func (s *TestSuite) TestUpdateContainerRecordCancelled(c *C) {
        err := cr.UpdateContainerRecordComplete()
        c.Check(err, IsNil)
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["log"], IsNil)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], IsNil)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], IsNil)
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], IsNil)
+       c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
 }
 
 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
@@ -463,12 +464,14 @@ func FullRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvT
 
        api = &ArvTestClient{ContainerRecord: rec}
        cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
+       am := &ArvMountCmdLine{}
+       cr.RunArvMount = am.ArvMountTest
 
        err = cr.Run()
        c.Check(err, IsNil)
        c.Check(api.WasSetRunning, Equals, true)
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
+       c.Check(api.Content[api.Calls-1]["container"].(arvadosclient.Dict)["log"], NotNil)
 
        if err != nil {
                for k, v := range api.Logs {
@@ -496,8 +499,9 @@ func (s *TestSuite) TestFullRunHello(c *C) {
                t.finish <- dockerclient.WaitResult{}
        })
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Calls, Equals, 7)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 
        c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
 
@@ -520,9 +524,10 @@ func (s *TestSuite) TestFullRunStderr(c *C) {
                t.finish <- dockerclient.WaitResult{ExitCode: 1}
        })
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Calls, Equals, 8)
+       c.Check(api.Content[7]["container"].(arvadosclient.Dict)["log"], NotNil)
+       c.Check(api.Content[7]["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
+       c.Check(api.Content[7]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 
        c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
        c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
@@ -544,8 +549,9 @@ func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
                t.finish <- dockerclient.WaitResult{ExitCode: 0}
        })
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Calls, Equals, 7)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 
        log.Print(api.Logs["stdout"].String())
 
@@ -568,8 +574,9 @@ func (s *TestSuite) TestFullRunSetCwd(c *C) {
                t.finish <- dockerclient.WaitResult{ExitCode: 0}
        })
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Calls, Equals, 7)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 
        c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
 }
@@ -601,6 +608,8 @@ func (s *TestSuite) TestCancel(c *C) {
 
        api := &ArvTestClient{ContainerRecord: rec}
        cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
+       am := &ArvMountCmdLine{}
+       cr.RunArvMount = am.ArvMountTest
 
        go func() {
                for cr.ContainerID == "" {
@@ -613,7 +622,8 @@ func (s *TestSuite) TestCancel(c *C) {
 
        c.Check(err, IsNil)
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
+       c.Check(api.Calls, Equals, 6)
+       c.Check(api.Content[5]["container"].(arvadosclient.Dict)["log"], NotNil)
 
        if err != nil {
                for k, v := range api.Logs {
@@ -622,7 +632,7 @@ func (s *TestSuite) TestCancel(c *C) {
                }
        }
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
+       c.Check(api.Content[5]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
 
        c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
 
@@ -644,8 +654,9 @@ func (s *TestSuite) TestFullRunSetEnv(c *C) {
                t.finish <- dockerclient.WaitResult{ExitCode: 0}
        })
 
-       c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
-       c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
+       c.Check(api.Calls, Equals, 7)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
+       c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
 
        c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
 }
index bce324d478571aefe0dddf0a0647199a3f0fc1e4..79214fca7dc4d3f5e0ac0b77475c6a0a5b27138d 100644 (file)
@@ -40,8 +40,8 @@ func (s *LoggingTestSuite) TestWriteLogs(c *C) {
        logtext := "2015-12-29T15:51:45.000000001Z Hello world!\n" +
                "2015-12-29T15:51:45.000000002Z Goodbye\n"
 
-       c.Check(api.Content["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
-       c.Check(api.Content["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext)
+       c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
+       c.Check(api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext)
        c.Check(string(kc.Content), Equals, logtext)
 }
 
@@ -83,14 +83,14 @@ func (s *LoggingTestSuite) TestWriteMultipleLogs(c *C) {
        cr.CrunchLog.Close()
        logtext1 := "2015-12-29T15:51:45.000000001Z Hello world!\n" +
                "2015-12-29T15:51:45.000000003Z Goodbye\n"
-       c.Check(api.Content["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
-       c.Check(api.Content["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext1)
+       c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
+       c.Check(api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext1)
 
        stdout.Close()
        logtext2 := "2015-12-29T15:51:45.000000002Z Doing stuff\n" +
                "2015-12-29T15:51:45.000000004Z Blurb\n"
-       c.Check(api.Content["log"].(arvadosclient.Dict)["event_type"], Equals, "stdout")
-       c.Check(api.Content["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext2)
+       c.Check(api.Content[1]["log"].(arvadosclient.Dict)["event_type"], Equals, "stdout")
+       c.Check(api.Content[1]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext2)
 
        mt, err := cr.LogCollection.ManifestText()
        c.Check(err, IsNil)
index 23b7c38e23c3cf2d83264e479c98acbf9d130aa6..a068a2a77b3c6805b1eecdfc73d1df2eefd50ed0 100644 (file)
@@ -103,7 +103,7 @@ func (m *CollectionFileWriter) goUpload() {
        finish <- errors
 }
 
-// CollectionWriter makes implements creating new Keep collections by opening files
+// CollectionWriter implements creating new Keep collections by opening files
 // and writing to them.
 type CollectionWriter struct {
        IKeepClient
index 1229f2917e21f9b17d897db8fe85e7adb4e43429..9b7eb7543a4ebed086aba2d409f44fcc789ef222 100644 (file)
@@ -130,8 +130,9 @@ func GetCollections(params GetCollectionsParams) (results ReadCollections, err e
 
        sdkParams := arvadosclient.Dict{
                "select":  fieldsWanted,
-               "order":   []string{"modified_at ASC"},
-               "filters": [][]string{[]string{"modified_at", ">=", "1900-01-01T00:00:00Z"}}}
+               "order":   []string{"modified_at ASC", "uuid ASC"},
+               "filters": [][]string{[]string{"modified_at", ">=", "1900-01-01T00:00:00Z"}},
+               "offset": 0}
 
        if params.BatchSize > 0 {
                sdkParams["limit"] = params.BatchSize
@@ -176,9 +177,10 @@ func GetCollections(params GetCollectionsParams) (results ReadCollections, err e
 
        // These values are just for getting the loop to run the first time,
        // afterwards they'll be set to real values.
-       previousTotalCollections := -1
-       totalCollections := 0
-       for totalCollections > previousTotalCollections {
+       remainingCollections := 1
+       var totalCollections int
+       var previousTotalCollections int
+       for remainingCollections > 0 {
                // We're still finding new collections
 
                // Write the heap profile for examining memory usage
@@ -193,6 +195,16 @@ func GetCollections(params GetCollectionsParams) (results ReadCollections, err e
                if err != nil {
                        return
                }
+               batchCollections := len(collections.Items)
+
+               // We must always have at least one collection in the batch
+               if batchCollections < 1 {
+                       err = fmt.Errorf("API query returned no collections for %+v", sdkParams)
+                       return
+               }
+
+               // Update count of remaining collections
+               remainingCollections = collections.ItemsAvailable - sdkParams["offset"].(int) - batchCollections
 
                // Process collection and update our date filter.
                latestModificationDate, maxManifestSize, totalManifestSize, err := ProcessCollections(params.Logger,
@@ -202,16 +214,24 @@ func GetCollections(params GetCollectionsParams) (results ReadCollections, err e
                if err != nil {
                        return results, err
                }
-               sdkParams["filters"].([][]string)[0][2] = latestModificationDate.Format(time.RFC3339)
+               if sdkParams["filters"].([][]string)[0][2] != latestModificationDate.Format(time.RFC3339) {
+                       sdkParams["filters"].([][]string)[0][2] = latestModificationDate.Format(time.RFC3339)
+                       sdkParams["offset"] = 0
+               } else {
+                       sdkParams["offset"] = sdkParams["offset"].(int) + batchCollections
+               }
 
                // update counts
                previousTotalCollections = totalCollections
                totalCollections = len(results.UUIDToCollection)
 
-               log.Printf("%d collections read, %d new in last batch, "+
+               log.Printf("%d collections read, %d (%d new) in last batch, "+
+                       "%d remaining, "+
                        "%s latest modified date, %.0f %d %d avg,max,total manifest size",
                        totalCollections,
+                       batchCollections,
                        totalCollections-previousTotalCollections,
+                       remainingCollections,
                        sdkParams["filters"].([][]string)[0][2],
                        float32(totalManifestSize)/float32(totalCollections),
                        maxManifestSize, totalManifestSize)
@@ -227,6 +247,30 @@ func GetCollections(params GetCollectionsParams) (results ReadCollections, err e
                }
        }
 
+       // Make one final API request to verify that we have processed all collections available up to the latest modification date
+       var collections SdkCollectionList
+       sdkParams["filters"].([][]string)[0][1] = "<="
+       sdkParams["limit"] = 0
+       err = params.Client.List("collections", sdkParams, &collections)
+       if err != nil {
+               return
+       }
+       finalNumberOfCollectionsAvailable, err :=
+               util.NumberItemsAvailable(params.Client, "collections")
+       if err != nil {
+               return
+       }
+       if totalCollections < finalNumberOfCollectionsAvailable {
+               err = fmt.Errorf("API server indicates a total of %d collections "+
+                               "available up to %v, but we only retrieved %d. "+
+                               "Refusing to continue as this could indicate an "+
+                               "otherwise undetected failure.",
+                               finalNumberOfCollectionsAvailable, 
+                               sdkParams["filters"].([][]string)[0][2],
+                               totalCollections)
+               return
+       }
+
        // Write the heap profile for examining memory usage
        err = WriteHeapProfile()
 
index 604a6db2f4a16ac97aea4579111f0d909c6c5d9e..8e128358422a560a42cbdaa03e20feb8067fa6ba 100644 (file)
@@ -22,6 +22,7 @@ var (
        logEventTypePrefix  string
        logFrequencySeconds int
        minutesBetweenRuns  int
+       collectionBatchSize int
        dryRun              bool
 )
 
@@ -38,6 +39,10 @@ func init() {
                "minutes-between-runs",
                0,
                "How many minutes we wait between data manager runs. 0 means run once and exit.")
+       flag.IntVar(&collectionBatchSize,
+               "collection-batch-size",
+               1000,
+               "How many collections to request in each batch.")
        flag.BoolVar(&dryRun,
                "dry-run",
                false,
@@ -193,7 +198,7 @@ func BuildDataFetcher(arv arvadosclient.ArvadosClient) summary.DataFetcher {
                                collection.GetCollectionsParams{
                                        Client:    arv,
                                        Logger:    arvLogger,
-                                       BatchSize: 50})
+                                       BatchSize: collectionBatchSize})
                        collDone <- struct{}{}
                }()
 
@@ -204,7 +209,7 @@ func BuildDataFetcher(arv arvadosclient.ArvadosClient) summary.DataFetcher {
                                Logger: arvLogger,
                                Limit:  1000})
 
-               <- collDone
+               <-collDone
 
                // Return a nil error only if both parts succeeded.
                if collErr != nil {
index 67b0e79f14fea4eb75a64d12f8907b9eabf4bf14..a99ec6b05252714c1ed3b21ce6dbf055a6dfd846 100644 (file)
@@ -12,6 +12,7 @@ import (
        "net/http"
        "os"
        "os/exec"
+       "path"
        "regexp"
        "strings"
        "testing"
@@ -537,12 +538,33 @@ func TestPutAndGetBlocks_NoErrorDuringSingleRun(t *testing.T) {
        testOldBlocksNotDeletedOnDataManagerError(t, "", "", false, false)
 }
 
+func createBadPath(t *testing.T) (badpath string) {
+       tempdir, err := ioutil.TempDir("", "bad")
+       if err != nil {
+               t.Fatalf("Could not create temporary directory for bad path: %v", err)
+       }
+       badpath = path.Join(tempdir, "bad")
+       return
+}
+
+func destroyBadPath(t *testing.T, badpath string) {
+       tempdir := path.Join(badpath, "..")
+       err := os.Remove(tempdir)
+       if err != nil {
+               t.Fatalf("Could not remove bad path temporary directory %v: %v", tempdir, err)
+       }
+}
+
 func TestPutAndGetBlocks_ErrorDuringGetCollectionsBadWriteTo(t *testing.T) {
-       testOldBlocksNotDeletedOnDataManagerError(t, "/badwritetofile", "", true, true)
+       badpath := createBadPath(t)
+       defer destroyBadPath(t, badpath)
+       testOldBlocksNotDeletedOnDataManagerError(t, path.Join(badpath, "writetofile"), "", true, true)
 }
 
 func TestPutAndGetBlocks_ErrorDuringGetCollectionsBadHeapProfileFilename(t *testing.T) {
-       testOldBlocksNotDeletedOnDataManagerError(t, "", "/badheapprofilefile", true, true)
+       badpath := createBadPath(t)
+       defer destroyBadPath(t, badpath)
+       testOldBlocksNotDeletedOnDataManagerError(t, "", path.Join(badpath, "heapprofilefile"), true, true)
 }
 
 // Create some blocks and backdate some of them.
diff --git a/services/nodemanager/arvnodeman/baseactor.py b/services/nodemanager/arvnodeman/baseactor.py
new file mode 100644 (file)
index 0000000..9591b42
--- /dev/null
@@ -0,0 +1,85 @@
+from __future__ import absolute_import, print_function
+
+import errno
+import logging
+import os
+import threading
+import traceback
+
+import pykka
+
+class _TellCallableProxy(object):
+    """Internal helper class for proxying callables."""
+
+    def __init__(self, ref, attr_path):
+        self.actor_ref = ref
+        self._attr_path = attr_path
+
+    def __call__(self, *args, **kwargs):
+        message = {
+            'command': 'pykka_call',
+            'attr_path': self._attr_path,
+            'args': args,
+            'kwargs': kwargs,
+        }
+        self.actor_ref.tell(message)
+
+
+class TellActorProxy(pykka.ActorProxy):
+    """ActorProxy in which all calls are implemented as using tell().
+
+    The standard pykka.ActorProxy always uses ask() and returns a Future.  If
+    the target method raises an exception, it is placed in the Future object
+    and re-raised when get() is called on the Future.  Unfortunately, most
+    messaging in Node Manager is asynchronous and the caller does not store the
+    Future object returned by the call to ActorProxy.  As a result, exceptions
+    resulting from these calls end up in limbo, neither reported in the logs
+    nor handled by on_failure().
+
+    The TellActorProxy uses tell() instead of ask() and does not return a
+    Future object.  As a result, if the target method raises an exception, it
+    will be logged and on_failure() will be called as intended.
+
+    """
+
+    def __repr__(self):
+        return '<ActorProxy for %s, attr_path=%s>' % (
+            self.actor_ref, self._attr_path)
+
+    def __getattr__(self, name):
+        """Get a callable from the actor."""
+        attr_path = self._attr_path + (name,)
+        if attr_path not in self._known_attrs:
+            self._known_attrs = self._get_attributes()
+        attr_info = self._known_attrs.get(attr_path)
+        if attr_info is None:
+            raise AttributeError('%s has no attribute "%s"' % (self, name))
+        if attr_info['callable']:
+            if attr_path not in self._callable_proxies:
+                self._callable_proxies[attr_path] = _TellCallableProxy(
+                    self.actor_ref, attr_path)
+            return self._callable_proxies[attr_path]
+        else:
+            raise AttributeError('attribute "%s" is not a callable on %s' % (name, self))
+
+class TellableActorRef(pykka.ActorRef):
+    """ActorRef adding the tell_proxy() method to get TellActorProxy."""
+
+    def tell_proxy(self):
+        return TellActorProxy(self)
+
+class BaseNodeManagerActor(pykka.ThreadingActor):
+    """Base class for actors in node manager, redefining actor_ref as a
+    TellableActorRef and providing a default on_failure handler.
+    """
+
+    def __init__(self, *args, **kwargs):
+         super(pykka.ThreadingActor, self).__init__(*args, **kwargs)
+         self.actor_ref = TellableActorRef(self)
+
+    def on_failure(self, exception_type, exception_value, tb):
+        lg = getattr(self, "_logger", logging)
+        if (exception_type in (threading.ThreadError, MemoryError) or
+            exception_type is OSError and exception_value.errno == errno.ENOMEM):
+            lg.critical("Unhandled exception is a fatal error, killing Node Manager")
+            os.killpg(os.getpgid(0), 9)
index 9a9ce588d382f54b9e399b5b280b8ed979557927..e1307494ec6644b10d8d1ca6954035223d94f5ee 100644 (file)
@@ -38,7 +38,7 @@ class RemotePollLoopActor(actor_class):
         super(RemotePollLoopActor, self).__init__()
         self._client = client
         self._timer = timer_actor
-        self._later = self.actor_ref.proxy()
+        self._later = self.actor_ref.tell_proxy()
         self._polling_started = False
         self.min_poll_wait = poll_wait
         self.max_poll_wait = max_poll_wait
index 2ae4fc8923612d474b833fcf9f345b255148ee3d..a239f1f252b5998d4e38a7bddbb0d7d4acdec3b3 100644 (file)
@@ -26,7 +26,7 @@ class ComputeNodeStateChangeBase(config.actor_class, RetryMixin):
         super(ComputeNodeStateChangeBase, self).__init__()
         RetryMixin.__init__(self, retry_wait, max_retry_wait,
                             None, cloud_client, timer_actor)
-        self._later = self.actor_ref.proxy()
+        self._later = self.actor_ref.tell_proxy()
         self._arvados = arvados_client
         self.subscribers = set()
 
@@ -37,7 +37,9 @@ class ComputeNodeStateChangeBase(config.actor_class, RetryMixin):
         self._set_logger()
 
     def _finished(self):
-        _notify_subscribers(self._later, self.subscribers)
+        if self.subscribers is None:
+            raise Exception("Actor tried to finish twice")
+        _notify_subscribers(self.actor_ref.proxy(), self.subscribers)
         self.subscribers = None
         self._logger.info("finished")
 
@@ -225,6 +227,7 @@ class ComputeNodeShutdownActor(ComputeNodeStateChangeBase):
         if not self._cloud.destroy_node(self.cloud_node):
             if self._cloud.broken(self.cloud_node):
                 self._later.cancel_shutdown(self.NODE_BROKEN)
+                return
             else:
                 # Force a retry.
                 raise cloud_types.LibcloudError("destroy_node failed")
@@ -304,7 +307,7 @@ class ComputeNodeMonitorActor(config.actor_class):
                  boot_fail_after=1800
     ):
         super(ComputeNodeMonitorActor, self).__init__()
-        self._later = self.actor_ref.proxy()
+        self._later = self.actor_ref.tell_proxy()
         self._last_log = None
         self._shutdowns = shutdown_timer
         self._cloud_node_fqdn = cloud_fqdn_func
@@ -388,7 +391,7 @@ class ComputeNodeMonitorActor(config.actor_class):
             eligible = self.shutdown_eligible()
             if eligible is True:
                 self._debug("Suggesting shutdown.")
-                _notify_subscribers(self._later, self.subscribers)
+                _notify_subscribers(self.actor_ref.proxy(), self.subscribers)
             elif self._shutdowns.window_open():
                 self._debug("Cannot shut down because %s", eligible)
             elif self.last_shutdown_opening != next_opening:
index c98c95af66d89b257be6f7e79d4fd4371138281c..9bdcc5f7a123e5a0d15dc237e2144bfe5358fdb4 100644 (file)
@@ -131,12 +131,9 @@ class BaseComputeNodeDriver(RetryMixin):
             self.ping_host, arvados_node['uuid'],
             arvados_node['info']['ping_secret'])
 
-    def find_node(self, name):
-        node = [n for n in self.list_nodes() if n.name == name]
-        if node:
-            return node[0]
-        else:
-            return None
+    @staticmethod
+    def _name_key(cloud_object):
+        return cloud_object.name
 
     def create_node(self, size, arvados_node):
         try:
@@ -151,15 +148,12 @@ class BaseComputeNodeDriver(RetryMixin):
             # loop forever because subsequent create_node attempts will fail
             # due to node name collision.  So check if the node we intended to
             # create shows up in the cloud node list and return it if found.
-            try:
-                node = self.find_node(kwargs['name'])
-                if node:
-                    return node
-            except:
-                # Ignore possible exception from find_node in favor of
-                # re-raising the original create_node exception.
-                pass
-            raise
+            node = self.search_for(kwargs['name'], 'list_nodes', self._name_key)
+            if node:
+                return node
+            else:
+                # something else went wrong, re-raise the exception
+                raise
 
     def post_create_node(self, cloud_node):
         # ComputeNodeSetupActor calls this method after the cloud node is
index d89c48e270bcc119638c70fc3d5f2928fbe1f8e3..991a2983c7217f1a29368293513587d117d01d59 100644 (file)
@@ -75,9 +75,6 @@ class ComputeNodeDriver(BaseComputeNodeDriver):
         self.real.ex_create_tags(cloud_node,
                                  {'Name': arvados_node_fqdn(arvados_node)})
 
-    def find_node(self, name):
-        raise NotImplementedError("ec2.ComputeNodeDriver.find_node")
-
     def list_nodes(self):
         # Need to populate Node.size
         nodes = super(ComputeNodeDriver, self).list_nodes()
index c5bf0b8cda42d211adcfbb61ffb3d73f460a7830..bbabdd4c761b5a0e3809449878227adea7db0a5a 100644 (file)
@@ -38,10 +38,6 @@ class ComputeNodeDriver(BaseComputeNodeDriver):
         self._disktype_links = {dt.name: self._object_link(dt)
                                 for dt in self.real.ex_list_disktypes()}
 
-    @staticmethod
-    def _name_key(cloud_object):
-        return cloud_object.name
-
     @staticmethod
     def _object_link(cloud_object):
         return cloud_object.extra.get('selfLink')
index dd45165deaa1514789c42428dacbb4bcf862b5a5..15891a92bcf8a7fdd5365595489e5efe96a5e0cd 100644 (file)
@@ -12,13 +12,15 @@ import httplib2
 import pykka
 from apiclient import errors as apierror
 
+from .baseactor import BaseNodeManagerActor
+
 # IOError is the base class for socket.error, ssl.SSLError, and friends.
 # It seems like it hits the sweet spot for operations we want to retry:
 # it's low-level, but unlikely to catch code bugs.
 NETWORK_ERRORS = (IOError,)
 ARVADOS_ERRORS = NETWORK_ERRORS + (apierror.Error,)
 
-actor_class = pykka.ThreadingActor
+actor_class = BaseNodeManagerActor
 
 class NodeManagerConfig(ConfigParser.SafeConfigParser):
     """Node Manager Configuration class.
index 0993c479625f23a209c90412fa4426ff2c406d23..7976f21f1a11b8083593a8e7ac9a68c494a47e16 100644 (file)
@@ -121,7 +121,7 @@ class NodeManagerDaemonActor(actor_class):
         self._new_arvados = arvados_factory
         self._new_cloud = cloud_factory
         self._cloud_driver = self._new_cloud()
-        self._later = self.actor_ref.proxy()
+        self._later = self.actor_ref.tell_proxy()
         self.shutdown_windows = shutdown_windows
         self.server_calculator = server_calculator
         self.min_cloud_size = self.server_calculator.cheapest_size()
@@ -174,11 +174,12 @@ class NodeManagerDaemonActor(actor_class):
             poll_stale_after=self.poll_stale_after,
             node_stale_after=self.node_stale_after,
             cloud_client=self._cloud_driver,
-            boot_fail_after=self.boot_fail_after).proxy()
-        actor.subscribe(self._later.node_can_shutdown)
+            boot_fail_after=self.boot_fail_after)
+        actorTell = actor.tell_proxy()
+        actorTell.subscribe(self._later.node_can_shutdown)
         self._cloud_nodes_actor.subscribe_to(cloud_node.id,
-                                             actor.update_cloud_node)
-        record = _ComputeNodeRecord(actor, cloud_node)
+                                             actorTell.update_cloud_node)
+        record = _ComputeNodeRecord(actor.proxy(), cloud_node)
         return record
 
     def update_cloud_nodes(self, nodelist):
@@ -410,10 +411,10 @@ class NodeManagerDaemonActor(actor_class):
         shutdown = self._node_shutdown.start(
             timer_actor=self._timer, cloud_client=self._new_cloud(),
             arvados_client=self._new_arvados(),
-            node_monitor=node_actor.actor_ref, cancellable=cancellable).proxy()
-        self.shutdowns[cloud_node_id] = shutdown
+            node_monitor=node_actor.actor_ref, cancellable=cancellable)
+        self.shutdowns[cloud_node_id] = shutdown.proxy()
         self.sizes_booting_shutdown[cloud_node_id] = cloud_node_obj.size
-        shutdown.subscribe(self._later.node_finished_shutdown)
+        shutdown.tell_proxy().subscribe(self._later.node_finished_shutdown)
 
     @_check_poll_freshness
     def node_can_shutdown(self, node_actor):
@@ -438,12 +439,10 @@ class NodeManagerDaemonActor(actor_class):
         if not success:
             if cancel_reason == self._node_shutdown.NODE_BROKEN:
                 self.cloud_nodes.blacklist(cloud_node_id)
-            del self.shutdowns[cloud_node_id]
-            del self.sizes_booting_shutdown[cloud_node_id]
         elif cloud_node_id in self.booted:
             self.booted.pop(cloud_node_id).actor.stop()
-            del self.shutdowns[cloud_node_id]
-            del self.sizes_booting_shutdown[cloud_node_id]
+        del self.shutdowns[cloud_node_id]
+        del self.sizes_booting_shutdown[cloud_node_id]
 
     def shutdown(self):
         self._logger.info("Shutting down after signal.")
index c8b3d19485b2c9cb8d6ee6e4353ddeb2c0b9c560..78bd2db5cc05fe9516c10e718506ef11734055db 100644 (file)
@@ -69,14 +69,14 @@ def launch_pollers(config, server_calculator):
     poll_time = config.getint('Daemon', 'poll_time')
     max_poll_time = config.getint('Daemon', 'max_poll_time')
 
-    timer = TimedCallBackActor.start(poll_time / 10.0).proxy()
+    timer = TimedCallBackActor.start(poll_time / 10.0).tell_proxy()
     cloud_node_poller = CloudNodeListMonitorActor.start(
-        config.new_cloud_client(), timer, poll_time, max_poll_time).proxy()
+        config.new_cloud_client(), timer, poll_time, max_poll_time).tell_proxy()
     arvados_node_poller = ArvadosNodeListMonitorActor.start(
-        config.new_arvados_client(), timer, poll_time, max_poll_time).proxy()
+        config.new_arvados_client(), timer, poll_time, max_poll_time).tell_proxy()
     job_queue_poller = JobQueueMonitorActor.start(
         config.new_arvados_client(), timer, server_calculator,
-        poll_time, max_poll_time).proxy()
+        poll_time, max_poll_time).tell_proxy()
     return timer, cloud_node_poller, arvados_node_poller, job_queue_poller
 
 _caught_signals = {}
@@ -110,7 +110,7 @@ def main(args=None):
         server_calculator = build_server_calculator(config)
         timer, cloud_node_poller, arvados_node_poller, job_queue_poller = \
             launch_pollers(config, server_calculator)
-        cloud_node_updater = node_update.start(config.new_cloud_client).proxy()
+        cloud_node_updater = node_update.start(config.new_cloud_client).tell_proxy()
         node_daemon = NodeManagerDaemonActor.start(
             job_queue_poller, arvados_node_poller, cloud_node_poller,
             cloud_node_updater, timer,
@@ -123,7 +123,7 @@ def main(args=None):
             config.getint('Daemon', 'boot_fail_after'),
             config.getint('Daemon', 'node_stale_after'),
             node_setup, node_shutdown, node_monitor,
-            max_total_price=config.getfloat('Daemon', 'max_total_price')).proxy()
+            max_total_price=config.getfloat('Daemon', 'max_total_price')).tell_proxy()
 
         signal.pause()
         daemon_stopped = node_daemon.actor_ref.actor_stopped.is_set
index 615f798f5b4ff045abb423e6b04f1339164ac44d..12d6280873e8fe23669bbf6f1dce08a952bfcda2 100644 (file)
@@ -18,7 +18,7 @@ class TimedCallBackActor(actor_class):
     """
     def __init__(self, max_sleep=1):
         super(TimedCallBackActor, self).__init__()
-        self._proxy = self.actor_ref.proxy()
+        self._proxy = self.actor_ref.tell_proxy()
         self.messages = []
         self.max_sleep = max_sleep
 
index f41fa6cb1af57b6b1cb005a09bec95207ace0b14..554fb88b4723463884f408bbb4454b279a208387 100644 (file)
@@ -26,6 +26,7 @@ class NodeManagerDaemonActorTestCase(testutil.ActorTestMixin,
                                           cloud_size=get_cloud_size,
                                           actor_ref=mock_actor)
         mock_actor.proxy.return_value = mock_proxy
+        mock_actor.tell_proxy.return_value = mock_proxy
 
         self.last_setup = mock_proxy
         return mock_actor
diff --git a/services/nodemanager/tests/test_failure.py b/services/nodemanager/tests/test_failure.py
new file mode 100644 (file)
index 0000000..35605fc
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import, print_function
+
+import errno
+import logging
+import threading
+import unittest
+
+import mock
+import pykka
+
+from . import testutil
+
+import arvnodeman.baseactor
+
+class BogusActor(arvnodeman.baseactor.BaseNodeManagerActor):
+    def __init__(self, e):
+        super(BogusActor, self).__init__()
+        self.exp = e
+
+    def doStuff(self):
+        raise self.exp
+
+class ActorUnhandledExceptionTest(unittest.TestCase):
+    def test_fatal_error(self):
+        for e in (MemoryError(), threading.ThreadError(), OSError(errno.ENOMEM, "")):
+            with mock.patch('os.killpg') as killpg_mock:
+                act = BogusActor.start(e).tell_proxy()
+                act.doStuff()
+                act.actor_ref.stop(block=True)
+                self.assertTrue(killpg_mock.called)
+
+    def test_nonfatal_error(self):
+        with mock.patch('os.killpg') as killpg_mock:
+            act = BogusActor.start(OSError(errno.ENOENT, "")).tell_proxy()
+            act.doStuff()
+            act.actor_ref.stop(block=True)
+            self.assertFalse(killpg_mock.called)
index 6cde766fa312f5b0e07ba53148a93844d26dbf47..5803b05318e795fc9ea7cf3c195d96a00ea9818b 100644 (file)
@@ -85,7 +85,10 @@ class MockTimer(object):
             to_deliver = self.messages
             self.messages = []
         for callback, args, kwargs in to_deliver:
-            callback(*args, **kwargs)
+            try:
+                callback(*args, **kwargs)
+            except pykka.ActorDeadError:
+                pass
 
     def schedule(self, want_time, callback, *args, **kwargs):
         with self.lock:
diff --git a/tools/arvbox/bin/arvbox b/tools/arvbox/bin/arvbox
new file mode 100755 (executable)
index 0000000..88726a4
--- /dev/null
@@ -0,0 +1,439 @@
+#!/bin/sh
+
+set -e
+
+if ! test -d /sys/fs/cgroup ; then
+     echo "Arvbox requires cgroups to be mounted at /sys/fs/cgroup in order to use"
+     echo "Docker-in-Docker.  Older operating systems that put cgroups in other"
+     echo "places (such as /cgroup) are not supported."
+     exit 1
+fi
+
+if ! which docker >/dev/null 2>/dev/null ; then
+  echo "Arvbox requires Docker.  To install, run the following command as root:"
+  echo "curl -sSL https://get.docker.com/ | sh"
+  exit 1
+fi
+
+if test -z "$ARVBOX_DOCKER" ; then
+    if which greadlink >/dev/null 2>/dev/null ; then
+        ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker)
+    else
+        ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker)
+    fi
+fi
+
+if test -z "$ARVBOX_CONTAINER" ; then
+    ARVBOX_CONTAINER=arvbox
+fi
+
+if test -z "$ARVBOX_BASE" ; then
+    ARVBOX_BASE="$HOME/.arvbox"
+fi
+
+if test -z "$ARVBOX_DATA" ; then
+    ARVBOX_DATA="$ARVBOX_BASE/$ARVBOX_CONTAINER"
+fi
+
+if test -z "$ARVADOS_ROOT" ; then
+    ARVADOS_ROOT="$ARVBOX_DATA/arvados"
+fi
+
+if test -z "$ARVADOS_DEV_ROOT" ; then
+    ARVADOS_DEV_ROOT="$ARVBOX_DATA/arvados-dev"
+fi
+
+if test -z "$SSO_ROOT" ; then
+    SSO_ROOT="$ARVBOX_DATA/sso-devise-omniauth-provider"
+fi
+
+PG_DATA="$ARVBOX_DATA/postgres"
+VAR_DATA="$ARVBOX_DATA/var"
+PASSENGER="$ARVBOX_DATA/passenger"
+GEMS="$ARVBOX_DATA/gems"
+
+getip() {
+    docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-
+}
+
+gethost() {
+    set +e
+    OVERRIDE=$(docker exec -i $ARVBOX_CONTAINER cat /var/run/localip_override 2>/dev/null)
+    CODE=$?
+    set -e
+    if test "$CODE" = 0 ; then
+       echo $OVERRIDE
+    else
+        getip
+    fi
+}
+
+updateconf() {
+    if test -f ~/.config/arvados/$ARVBOX_CONTAINER.conf ; then
+        sed "s/ARVADOS_API_HOST=.*/ARVADOS_API_HOST=$(gethost):8000/" <$HOME/.config/arvados/$ARVBOX_CONTAINER.conf >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf.tmp
+        mv ~/.config/arvados/$ARVBOX_CONTAINER.conf.tmp ~/.config/arvados/$ARVBOX_CONTAINER.conf
+    else
+        mkdir -p $HOME/.config/arvados
+        cat >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf <<EOF
+ARVADOS_API_HOST=$(gethost):8000
+ARVADOS_API_TOKEN=
+ARVADOS_API_HOST_INSECURE=true
+EOF
+    fi
+}
+
+wait_for_arvbox() {
+    FF=/tmp/arvbox-fifo-$$
+    mkfifo $FF
+    docker logs -f $ARVBOX_CONTAINER > $FF &
+    LOGPID=$!
+    while read line ; do
+        echo $line
+        if echo $line | grep "Workbench is running at" >/dev/null ; then
+            kill $LOGPID
+        fi
+    done < $FF
+    rm $FF
+    echo
+    if test -n "$localip" ; then
+        echo "export ARVADOS_API_HOST=$localip:8000"
+    else
+        echo "export ARVADOS_API_HOST=$(gethost):8000"
+    fi
+}
+
+run() {
+    if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        echo "Container $ARVBOX_CONTAINER is already running, use stop, restart or rebuild"
+        exit 1
+    fi
+
+    if echo "$1" | grep '^public' ; then
+        if test -n "$ARVBOX_PUBLISH_IP" ; then
+            localip=$ARVBOX_PUBLISH_IP
+        else
+            defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
+            localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+        fi
+        iptemp=$(tempfile)
+        echo $localip > $iptemp
+        chmod og+r $iptemp
+        PUBLIC="--volume=$iptemp:/var/run/localip_override
+              --publish=80:80
+              --publish=8000:8000
+              --publish=8900:8900
+              --publish=9001:9001
+              --publish=9002:9002
+              --publish=25100:25100
+              --publish=25107:25107
+              --publish=25108:25108
+              --publish=8001:8001"
+    else
+        PUBLIC=""
+    fi
+
+    if echo "$1" | grep 'demo$' ; then
+        if test -d "$ARVBOX_DATA" ; then
+            echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
+            echo "Set ARVBOX_CONTAINER to set a different name for your demo container"
+            exit 1
+        fi
+
+        if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
+            docker create -v /var/lib/postgresql -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
+        fi
+
+        docker run \
+               --detach \
+               --name=$ARVBOX_CONTAINER \
+               --privileged \
+               --volumes-from $ARVBOX_CONTAINER-data \
+               $PUBLIC \
+               arvados/arvbox-demo
+        updateconf
+        wait_for_arvbox
+    else
+        mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS"
+
+        if ! test -d "$ARVADOS_ROOT" ; then
+            git clone https://github.com/curoverse/arvados.git "$ARVADOS_ROOT"
+        fi
+        if ! test -d "$SSO_ROOT" ; then
+            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git "$SSO_ROOT"
+        fi
+
+        if test "$1" = test ; then
+            shift
+
+            if ! test -d "$ARVADOS_DEV_ROOT" ; then
+                git clone https://github.com/curoverse/arvados-dev.git "$ARVADOS_DEV_ROOT"
+            fi
+
+            mkdir -p $VAR_DATA/test
+
+            docker run \
+                   --detach \
+                   --name=$ARVBOX_CONTAINER \
+                   --privileged \
+                   "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
+                   "--volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw" \
+                   "--volume=$SSO_ROOT:/usr/src/sso:rw" \
+                   "--volume=$PG_DATA:/var/lib/postgresql:rw" \
+                   "--volume=$VAR_DATA:/var/lib/arvados:rw" \
+                   "--volume=$PASSENGER:/var/lib/passenger:rw" \
+                   "--volume=$GEMS:/var/lib/gems:rw" \
+                   arvados/arvbox-dev \
+                   /usr/local/bin/runsvinit -svdir=/etc/test-service
+
+            docker exec -ti \
+                    $ARVBOX_CONTAINER \
+                    /usr/local/lib/arvbox/runsu.sh \
+                    /usr/local/lib/arvbox/waitforpostgres.sh
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /usr/local/lib/arvbox/runsu.sh \
+                   /var/lib/arvbox/service/sso/run-service --only-setup
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /usr/local/lib/arvbox/runsu.sh \
+                   /var/lib/arvbox/service/api/run-service --only-setup
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /usr/local/lib/arvbox/runsu.sh \
+                   /usr/src/arvados-dev/jenkins/run-tests.sh \
+                   --temp /var/lib/arvados/test \
+                   WORKSPACE=/usr/src/arvados \
+                   GEM_HOME=/var/lib/gems \
+                   "$@"
+        elif echo "$1" | grep 'dev$' ; then
+            docker run \
+                   --detach \
+                   --name=$ARVBOX_CONTAINER \
+                   --privileged \
+                   "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
+                   "--volume=$SSO_ROOT:/usr/src/sso:rw" \
+                   "--volume=$PG_DATA:/var/lib/postgresql:rw" \
+                   "--volume=$VAR_DATA:/var/lib/arvados:rw" \
+                   "--volume=$PASSENGER:/var/lib/passenger:rw" \
+                   "--volume=$GEMS:/var/lib/gems:rw" \
+                   $PUBLIC \
+                   arvados/arvbox-dev
+            updateconf
+            wait_for_arvbox
+            echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
+        else
+            echo "Unknown configuration '$1'"
+        fi
+    fi
+}
+
+stop() {
+    if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker stop $ARVBOX_CONTAINER
+    fi
+
+    VOLUMES=--volumes=true
+    if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker rm $VOLUMES $ARVBOX_CONTAINER
+    fi
+    if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker rm $VOLUMES $ARVBOX_CONTAINER
+    fi
+}
+
+build() {
+    if ! test -f "$ARVBOX_DOCKER/Dockerfile.base" ;  then
+        echo "Could not find Dockerfile (expected it at $ARVBOX_DOCKER/Dockerfile.base)"
+        exit 1
+    fi
+    docker build -t arvados/arvbox-base -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
+    if test "$1" = localdemo -o "$1" = publicdemo ; then
+        docker build -t arvados/arvbox-demo -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
+    else
+        docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
+    fi
+}
+
+check() {
+    case "$1" in
+        localdemo|publicdemo|dev|publicdev|test)
+            true
+            ;;
+        *)
+            echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test"
+            exit 1
+        ;;
+    esac
+}
+
+subcmd="$1"
+if test -n "$subcmd" ; then
+    shift
+fi
+case "$subcmd" in
+    build)
+        check $@
+        build $@
+        ;;
+
+    start|run)
+        check $@
+        run $@
+        ;;
+
+    sh*)
+        exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM GEM_HOME=/var/lib/gems /bin/bash
+        ;;
+
+    pipe)
+        exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env GEM_HOME=/var/lib/gems /bin/bash -
+        ;;
+
+    stop)
+        stop
+        ;;
+
+    restart)
+        check $@
+        stop
+        run $@
+        ;;
+
+    rebuild)
+        check $@
+        stop
+        build $@
+        run $@
+        ;;
+
+    ip)
+        getip
+        ;;
+
+    host)
+        gethost
+        ;;
+
+    open)
+        exec xdg-open http://$(gethost)
+        ;;
+
+    status)
+        echo "Selected: $ARVBOX_CONTAINER"
+        if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+            echo "Status: running"
+            echo "Container IP: $(getip)"
+            echo "Published host: $(gethost)"
+        else
+            echo "Status: not running"
+        fi
+        if test -d "$ARVBOX_DATA" ; then
+            echo "Data: $ARVBOX_DATA"
+        elif docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q ; then
+            echo "Data: $ARVBOX_CONTAINER-data"
+        else
+            echo "Data: none"
+        fi
+        ;;
+
+    reset|destroy)
+        stop
+        if test -d "$ARVBOX_DATA" ; then
+            if test "$subcmd" = destroy ; then
+                if test "$1" != -f ; then
+                    echo "WARNING!  This will delete your entire arvbox ($ARVBOX_DATA)."
+                    echo "Use destroy -f if you really mean it."
+                    exit 1
+                fi
+                set -x
+                rm -rf "$ARVBOX_DATA"
+            else
+                if test "$1" != -f ; then
+                    echo "WARNING!  This will delete your arvbox data ($ARVBOX_DATA)."
+                    echo "Code and downloaded packages will be preserved."
+                    echo "Use reset -f if you really mean it."
+                    exit 1
+                fi
+                set -x
+                rm -rf "$ARVBOX_DATA/postgres"
+                rm -rf "$ARVBOX_DATA/var"
+            fi
+        else
+            if test "$1" != -f ; then
+                echo "WARNING!  This will delete your data container $ARVBOX_CONTAINER-data.  Use -f if you really mean it."
+                exit 1
+            fi
+            set -x
+            docker rm "$ARVBOX_CONTAINER-data"
+        fi
+        ;;
+
+    log)
+        if test -n "$1" ; then
+            exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM less --follow-name +GF "/etc/service/$1/log/main/current"
+        else
+            exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM tail $(docker exec -ti $ARVBOX_CONTAINER find -L /etc -path '/etc/service/*/log/main/current' -printf " %p")
+        fi
+        ;;
+
+    cat)
+        if test -n "$1" ; then
+            exec docker exec -ti $ARVBOX_CONTAINER cat "$@"
+        else
+            echo "Usage: $0 $subcmd <files>"
+        fi
+        ;;
+
+    ls)
+        exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM ls "$@"
+        ;;
+
+    sv)
+        if test -n "$1" -a -n "$2" ; then
+            exec docker exec -ti $ARVBOX_CONTAINER sv "$@"
+        else
+            echo "Usage: $0 $subcmd <start|stop|restart> <service>"
+            echo "Available services:"
+            exec docker exec -ti $ARVBOX_CONTAINER ls /etc/service
+        fi
+        ;;
+
+    clone)
+        if test -n "$2" ; then
+            cp -r "$ARVBOX_BASE/$1" "$ARVBOX_BASE/$2"
+            echo "Created new arvbox $2"
+            echo "export ARVBOX_CONTAINER=$2"
+        else
+            echo "clone <from> <to>   clone an arvbox"
+            echo "available arvboxes: $(ls $ARVBOX_BASE)"
+        fi
+        ;;
+
+    *)
+        echo "Arvados-in-a-box                      http://arvados.org"
+        echo
+        echo "$(basename $0) (build|start|run|open|shell|ip|stop|rebuild|reset|destroy|log|svrestart)"
+        echo
+        echo "build <config>      build arvbox Docker image"
+        echo "start|run <config>  start $ARVBOX_CONTAINER container"
+        echo "open       open arvbox workbench in a web browser"
+        echo "shell      enter arvbox shell"
+        echo "ip         print arvbox docker container ip address"
+        echo "host       print arvbox published host"
+        echo "status     print some information about current arvbox"
+        echo "stop       stop arvbox container"
+        echo "restart <config>  stop, then run again"
+        echo "rebuild <config>  stop, build arvbox Docker image, run"
+        echo "reset      delete arvbox arvados data (be careful!)"
+        echo "destroy    delete all arvbox code and data (be careful!)"
+        echo "log <service> tail log of specified service"
+        echo "ls <options>  list directories inside arvbox"
+        echo "cat <files>   get contents of files inside arvbox"
+        echo "pipe       run a bash script piped in from stdin"
+        echo "sv <start|stop|restart> <service> change state of service inside arvbox"
+        echo "clone <from> <to>   clone an arvbox"
+        ;;
+esac
diff --git a/tools/arvbox/lib/arvbox/docker/Dockerfile.base b/tools/arvbox/lib/arvbox/docker/Dockerfile.base
new file mode 100644 (file)
index 0000000..280ac68
--- /dev/null
@@ -0,0 +1,42 @@
+FROM debian:8
+
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
+    postgresql-9.4 git gcc golang-go runit \
+    ruby rake bundler curl libpq-dev \
+    libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
+    openssh-server python-setuptools netcat-traditional \
+    libpython-dev fuse libfuse-dev python-pip python-yaml \
+    pkg-config libattr1-dev python-llfuse python-pycurl \
+    libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
+    libjson-perl nginx gitolite3 lsof python-epydoc graphviz \
+    apt-transport-https ca-certificates slurm-wlm
+
+VOLUME /var/lib/docker
+VOLUME /var/log/nginx
+VOLUME /etc/ssl/private
+
+RUN apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D || \
+    apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
+
+RUN mkdir -p /etc/apt/sources.list.d && \
+    echo deb https://apt.dockerproject.org/repo debian-jessie main > /etc/apt/sources.list.d/docker.list && \
+    apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install docker-engine=1.9.1-0~jessie
+
+RUN rm -rf /var/lib/postgresql && mkdir -p /var/lib/postgresql
+
+RUN cd /root && \
+    GOPATH=$PWD go get github.com/curoverse/runsvinit && \
+    install bin/runsvinit /usr/local/bin
+
+ADD fuse.conf /etc/
+
+ADD crunch-setup.sh gitolite.rc \
+    keep-setup.sh common.sh createusers.sh \
+    logger runsu.sh waitforpostgres.sh \
+    application_yml_override.py \
+    /usr/local/lib/arvbox/
+
+# Start the supervisor.
+CMD ["/usr/local/bin/runsvinit"]
diff --git a/tools/arvbox/lib/arvbox/docker/Dockerfile.demo b/tools/arvbox/lib/arvbox/docker/Dockerfile.demo
new file mode 100644 (file)
index 0000000..1f13415
--- /dev/null
@@ -0,0 +1,17 @@
+FROM arvados/arvbox-base
+
+RUN cd /usr/src && \
+    git clone https://github.com/curoverse/arvados.git && \
+    git clone https://github.com/curoverse/sso-devise-omniauth-provider.git sso
+
+ADD service/ /var/lib/arvbox/service
+RUN rmdir /etc/service && ln -sf /var/lib/arvbox/service /etc
+
+RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
+
+RUN sudo -u arvbox /var/lib/arvbox/service/sso/run-service --only-deps
+RUN sudo -u arvbox /var/lib/arvbox/service/api/run-service --only-deps
+RUN sudo -u arvbox /var/lib/arvbox/service/workbench/run-service --only-deps
+RUN sudo -u arvbox /var/lib/arvbox/service/doc/run-service --only-deps
+RUN sudo -u arvbox /var/lib/arvbox/service/vm/run-service --only-deps
+RUN sudo -u arvbox /var/lib/arvbox/service/sdk/run-service
diff --git a/tools/arvbox/lib/arvbox/docker/Dockerfile.dev b/tools/arvbox/lib/arvbox/docker/Dockerfile.dev
new file mode 100644 (file)
index 0000000..051c274
--- /dev/null
@@ -0,0 +1,16 @@
+FROM arvados/arvbox-base
+
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
+    python-virtualenv python3-virtualenv linkchecker xvfb iceweasel
+
+RUN set -e && \
+ PJS=phantomjs-1.9.7-linux-x86_64 && \
+ curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
+ tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
+ ln -s ../$PJS/bin/phantomjs /usr/local/bin/
+
+ADD service/ /var/lib/arvbox/service
+RUN rmdir /etc/service && ln -sf /var/lib/arvbox/service /etc
+
+RUN mkdir /etc/test-service && ln -sf /var/lib/arvbox/service/postgres /etc/test-service
diff --git a/tools/arvbox/lib/arvbox/docker/application_yml_override.py b/tools/arvbox/lib/arvbox/docker/application_yml_override.py
new file mode 100755 (executable)
index 0000000..98a8e48
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+import yaml
+
+try:
+    with open("application.yml.override") as f:
+        b = yaml.load(f)
+except IOError:
+    exit()
+
+with open("application.yml") as f:
+    a = yaml.load(f)
+
+def recursiveMerge(a, b):
+    if isinstance(a, dict) and isinstance(b, dict):
+        for k in b:
+            print k
+            a[k] = recursiveMerge(a.get(k), b[k])
+        return a
+    else:
+        return b
+
+with open("application.yml", "w") as f:
+    yaml.dump(recursiveMerge(a, b), f)
diff --git a/tools/arvbox/lib/arvbox/docker/common.sh b/tools/arvbox/lib/arvbox/docker/common.sh
new file mode 100644 (file)
index 0000000..4c2de47
--- /dev/null
@@ -0,0 +1,62 @@
+
+if test -s /var/run/localip_override ; then
+    localip=$(cat /var/run/localip_override)
+else
+    defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
+    localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+fi
+
+export GEM_HOME=/var/lib/gems
+export GEM_PATH=/var/lib/gems
+
+declare -A services
+services=(
+  [workbench]=80
+  [api]=8000
+  [sso]=8900
+  [arv-git-httpd]=9001
+  [keep-web]=9002
+  [keepproxy]=25100
+  [keepstore0]=25107
+  [keepstore1]=25108
+  [ssh]=22
+  [doc]=8001
+)
+
+if test "$(id arvbox -u 2>/dev/null)" = 0 ; then
+    PGUSER=postgres
+    PGGROUP=postgres
+else
+    PGUSER=arvbox
+    PGGROUP=arvbox
+fi
+
+run_bundler() {
+    if test -f Gemfile.lock ; then
+        frozen=--frozen
+    else
+        frozen=""
+    fi
+    if ! flock /var/lib/arvados/gems.lock bundle install --path $GEM_HOME --local --no-deployment $frozen "$@" ; then
+        flock /var/lib/arvados/gems.lock bundle install --path $GEM_HOME --no-deployment $frozen "$@"
+    fi
+}
+
+pip_install() {
+    pushd /var/lib/arvados/pip
+    for p in $(ls http*.tar.gz) ; do
+        if test -f $p ; then
+            ln -sf $p $(echo $p | sed 's/.*%2F\(.*\)/\1/')
+        fi
+    done
+    for p in $(ls http*.whl) ; do
+        if test -f $p ; then
+            ln -sf $p $(echo $p | sed 's/.*%2F\(.*\)/\1/')
+        fi
+    done
+    popd
+
+    if ! pip install --no-index --find-links /var/lib/arvados/pip $1 ; then
+        pip install $1
+    fi
+}
diff --git a/tools/arvbox/lib/arvbox/docker/createusers.sh b/tools/arvbox/lib/arvbox/docker/createusers.sh
new file mode 100755 (executable)
index 0000000..b77c9c2
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+set -e -o pipefail
+
+if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
+    HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
+    HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
+    FUSEGID=$(ls -nd /dev/fuse | sed 's/ */ /' | cut -d' ' -f5)
+
+    mkdir -p /var/lib/arvados/git /var/lib/gems /var/lib/passenger
+
+    groupadd --gid $HOSTGID --non-unique arvbox
+    groupadd --gid $FUSEGID --non-unique fuse
+    groupadd --gid $HOSTGID --non-unique git
+    useradd --home-dir /var/lib/arvados \
+            --uid $HOSTUID --gid $HOSTGID \
+            --non-unique \
+            --groups docker,fuse \
+            arvbox
+    useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
+    useradd --groups docker,fuse crunch
+
+    chown arvbox:arvbox -R /usr/local /var/lib/arvados /var/lib/gems \
+          /var/lib/passenger /var/lib/postgresql \
+          /var/lib/nginx /var/log/nginx /etc/ssl/private
+
+    mkdir -p /var/lib/gems/ruby/2.1.0
+    chown arvbox:arvbox -R /var/lib/gems/ruby/2.1.0
+
+    mkdir -p /tmp/crunch0 /tmp/crunch1
+    chown crunch:crunch -R /tmp/crunch0 /tmp/crunch1
+
+    echo "arvbox    ALL=(crunch) NOPASSWD: ALL" >> /etc/sudoers
+fi
diff --git a/tools/arvbox/lib/arvbox/docker/crunch-setup.sh b/tools/arvbox/lib/arvbox/docker/crunch-setup.sh
new file mode 100755 (executable)
index 0000000..178fec1
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/crunchstat"
+install bin/crunchstat /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export CRUNCH_JOB_BIN=/usr/src/arvados/sdk/cli/bin/crunch-job
+export PERLLIB=/usr/src/arvados/sdk/perl/lib
+export CRUNCH_TMP=/tmp/$1
+export CRUNCH_DISPATCH_LOCKFILE=/var/lock/$1-dispatch
+export CRUNCH_JOB_DOCKER_BIN=docker
+export HOME=/tmp/$1
+
+cd /usr/src/arvados/services/api
+exec bundle exec ./script/crunch-dispatch.rb development
diff --git a/tools/arvbox/lib/arvbox/docker/fuse.conf b/tools/arvbox/lib/arvbox/docker/fuse.conf
new file mode 100644 (file)
index 0000000..a439ab8
--- /dev/null
@@ -0,0 +1 @@
+user_allow_other
diff --git a/tools/arvbox/lib/arvbox/docker/gitolite.rc b/tools/arvbox/lib/arvbox/docker/gitolite.rc
new file mode 100644 (file)
index 0000000..03c4b29
--- /dev/null
@@ -0,0 +1,213 @@
+# This is based on the default Gitolite configuration file with the following
+# changes applied as described here:
+# http://doc.arvados.org/install/install-arv-git-httpd.html
+
+# configuration variables for gitolite
+
+# This file is in perl syntax.  But you do NOT need to know perl to edit it --
+# just mind the commas, use single quotes unless you know what you're doing,
+# and make sure the brackets and braces stay matched up!
+
+# (Tip: perl allows a comma after the last item in a list also!)
+
+# HELP for commands can be had by running the command with "-h".
+
+# HELP for all the other FEATURES can be found in the documentation (look for
+# "list of non-core programs shipped with gitolite" in the master index) or
+# directly in the corresponding source file.
+
+my $repo_aliases;
+my $aliases_src = "$ENV{HOME}/.gitolite/arvadosaliases.pl";
+if ($ENV{HOME} && (-e $aliases_src)) {
+    $repo_aliases = do $aliases_src;
+}
+$repo_aliases ||= {};
+
+%RC = (
+
+    REPO_ALIASES => $repo_aliases,
+
+    # ------------------------------------------------------------------
+
+    # default umask gives you perms of '0700'; see the rc file docs for
+    # how/why you might change this
+    UMASK                           =>  0022,
+
+    # look for "git-config" in the documentation
+    GIT_CONFIG_KEYS                 =>  '',
+
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                       =>  1,
+    # logging options
+    # 1. leave this section as is for 'normal' gitolite logging (default)
+    # 2. uncomment this line to log ONLY to syslog:
+    # LOG_DEST                      => 'syslog',
+    # 3. uncomment this line to log to syslog and the normal gitolite log:
+    # LOG_DEST                      => 'syslog,normal',
+    # 4. prefixing "repo-log," to any of the above will **also** log just the
+    #    update records to "gl-log" in the bare repo directory:
+    # LOG_DEST                      => 'repo-log,normal',
+    # LOG_DEST                      => 'repo-log,syslog',
+    # LOG_DEST                      => 'repo-log,syslog,normal',
+
+    # roles.  add more roles (like MANAGER, TESTER, ...) here.
+    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
+    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
+    ROLES => {
+        READERS                     =>  1,
+        WRITERS                     =>  1,
+    },
+
+    # enable caching (currently only Redis).  PLEASE RTFM BEFORE USING!!!
+    # CACHE                         =>  'Redis',
+
+    # ------------------------------------------------------------------
+
+    # rc variables used by various features
+
+    # the 'info' command prints this as additional info, if it is set
+        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
+    # the CpuTime feature uses these
+        # display user, system, and elapsed times to user after each git operation
+        # DISPLAY_CPU_TIME          =>  1,
+        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
+        # CPU_TIME_WARN_LIMIT       =>  0.1,
+
+    # the Mirroring feature needs this
+        # HOSTNAME                  =>  "foo",
+
+    # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
+        # CACHE_TTL                 =>  600,
+
+    # ------------------------------------------------------------------
+
+    # suggested locations for site-local gitolite code (see cust.html)
+
+        # this one is managed directly on the server
+        # LOCAL_CODE                =>  "$ENV{HOME}/local",
+
+        # or you can use this, which lets you put everything in a subdirectory
+        # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
+        # on this, see http://gitolite.com/gitolite/non-core.html#pushcode
+        # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
+
+    # ------------------------------------------------------------------
+
+    # List of commands and features to enable
+
+    ENABLE => [
+
+        # COMMANDS
+
+            # These are the commands enabled by default
+            'help',
+            'desc',
+            'info',
+            'perms',
+            'writable',
+
+            # Uncomment or add new commands here.
+            # 'create',
+            # 'fork',
+            # 'mirror',
+            # 'readme',
+            # 'sskm',
+            # 'D',
+
+        # These FEATURES are enabled by default.
+
+            # essential (unless you're using smart-http mode)
+            'ssh-authkeys',
+
+            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
+            'git-config',
+
+            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
+            'daemon',
+
+            # creates projects.list file; if you don't use gitweb, comment this out
+            'gitweb',
+
+        # These FEATURES are disabled by default; uncomment to enable.  If you
+        # need to add new ones, ask on the mailing list :-)
+
+        # user-visible behaviour
+
+            # prevent wild repos auto-create on fetch/clone
+            # 'no-create-on-read',
+            # no auto-create at all (don't forget to enable the 'create' command!)
+            # 'no-auto-create',
+
+            # access a repo by another (possibly legacy) name
+            'Alias',
+
+            # give some users direct shell access.  See documentation in
+            # sts.html for details on the following two choices.
+            # "Shell $ENV{HOME}/.gitolite.shell-users",
+            # 'Shell alice bob',
+
+            # set default roles from lines like 'option default.roles-1 = ...', etc.
+            # 'set-default-roles',
+
+            # show more detailed messages on deny
+            # 'expand-deny-messages',
+
+            # show a message of the day
+            # 'Motd',
+
+        # system admin stuff
+
+            # enable mirroring (don't forget to set the HOSTNAME too!)
+            # 'Mirroring',
+
+            # allow people to submit pub files with more than one key in them
+            # 'ssh-authkeys-split',
+
+            # selective read control hack
+            # 'partial-copy',
+
+            # manage local, gitolite-controlled, copies of read-only upstream repos
+            # 'upstream',
+
+            # updates 'description' file instead of 'gitweb.description' config item
+            # 'cgit',
+
+            # allow repo-specific hooks to be added
+            # 'repo-specific-hooks',
+
+        # performance, logging, monitoring...
+
+            # be nice
+            # 'renice 10',
+
+            # log CPU times (user, system, cumulative user, cumulative system)
+            # 'CpuTime',
+
+        # syntactic_sugar for gitolite.conf and included files
+
+            # allow backslash-escaped continuation lines in gitolite.conf
+            # 'continuation-lines',
+
+            # create implicit user groups from directory names in keydir/
+            # 'keysubdirs-as-groups',
+
+            # allow simple line-oriented macros
+            # 'macros',
+
+        # Kindergarten mode
+
+            # disallow various things that sensible people shouldn't be doing anyway
+            # 'Kindergarten',
+    ],
+
+);
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:
diff --git a/tools/arvbox/lib/arvbox/docker/keep-setup.sh b/tools/arvbox/lib/arvbox/docker/keep-setup.sh
new file mode 100755 (executable)
index 0000000..b66463f
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+exec 2>&1
+sleep 2
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keepstore"
+install bin/keepstore /usr/local/bin
+
+mkdir -p /var/lib/arvados/$1
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+
+set +e
+read -rd $'\000' keepservice <<EOF
+{
+ "service_host":"$localip",
+ "service_port":$2,
+ "service_ssl_flag":false,
+ "service_type":"disk"
+}
+EOF
+set -e
+
+if test -s /var/lib/arvados/$1-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/$1-uuid)
+    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
+else
+    UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
+    echo $UUID > /var/lib/arvados/$1-uuid
+fi
+
+set +e
+killall -HUP keepproxy
+
+exec /usr/local/bin/keepstore \
+     -listen=:$2 \
+     -enforce-permissions=true \
+     -blob-signing-key-file=/var/lib/arvados/blob_signing_key \
+     -max-buffers=20 \
+     -volume=/var/lib/arvados/$1
diff --git a/tools/arvbox/lib/arvbox/docker/logger b/tools/arvbox/lib/arvbox/docker/logger
new file mode 100755 (executable)
index 0000000..a79a518
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec svlogd -tt ./main
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/.gitignore b/tools/arvbox/lib/arvbox/docker/runit-docker/.gitignore
new file mode 100644 (file)
index 0000000..bbf313b
--- /dev/null
@@ -0,0 +1,32 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/LICENSE b/tools/arvbox/lib/arvbox/docker/runit-docker/LICENSE
new file mode 100644 (file)
index 0000000..d158667
--- /dev/null
@@ -0,0 +1,28 @@
+Copyright (c) 2015, Kosma Moczek
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of runit-docker nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/Makefile b/tools/arvbox/lib/arvbox/docker/runit-docker/Makefile
new file mode 100644 (file)
index 0000000..9a28963
--- /dev/null
@@ -0,0 +1,18 @@
+CFLAGS=-std=c99 -Wall -O2 -fPIC -D_POSIX_SOURCE -D_GNU_SOURCE
+LDLIBS=-ldl
+
+PROGNAME=runit-docker
+
+all: $(PROGNAME).so
+
+%.so: %.c
+       gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
+
+install: runit-docker.so
+       mkdir -p $(DESTDIR)/sbin
+       mkdir -p $(DESTDIR)/lib
+       install -m 755 $(PROGNAME) $(DESTDIR)/sbin/
+       install -m 755 $(PROGNAME).so $(DESTDIR)/lib/
+
+clean:
+       $(RM) $(PROGNAME).so
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/README.md b/tools/arvbox/lib/arvbox/docker/runit-docker/README.md
new file mode 100644 (file)
index 0000000..1bcb8cc
--- /dev/null
@@ -0,0 +1,24 @@
+# runit-docker
+
+Docker and `runsvdir` don't quite agree on what each signal means, causing
+TONS of frustration when attempting to use `runsvdir` as init under Docker.
+`runit-docker` is a plug'n'play adapter library which does signal translation
+without the overhead and nuisance of running a nanny process.
+
+## Features
+
+* Pressing Ctrl-C does a clean shutdown.
+* `docker stop` does a clean shutdown.
+
+Under the hood, `runit-docker` translates `SIGTERM` and `SIGINT` to `SIGHUP`.
+
+## Usage
+
+* Build with `make`, install with `make install`.
+* Add `CMD ["/sbin/runit-docker"]` to your `Dockerfile`.
+* Run `debian/rules clean build binary` to build a Debian package.
+
+## Author
+
+runit-docker was written by Kosma Moczek &lt;kosma.moczek@pixers.pl&gt; during a single Scrum
+planning meeting. Damn meetings.
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/changelog b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/changelog
new file mode 100644 (file)
index 0000000..7d8689f
--- /dev/null
@@ -0,0 +1,12 @@
+runit-docker (1.1) unstable; urgency=low
+
+  * Simplify logic.
+  * Install for SIGINT as well.
+
+ -- Kosma Moczek <kosma@kosma.pl>  Mon, 11 May 2015 12:23:59 +0000
+
+runit-docker (1.0) unstable; urgency=low
+
+  * Initial release
+
+ -- Kosma Moczek <kosma@kosma.pl>  Mon, 11 May 2015 12:23:59 +0000
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/compat b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/control b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/control
new file mode 100644 (file)
index 0000000..4060915
--- /dev/null
@@ -0,0 +1,14 @@
+Source: runit-docker
+Section: contrib/admin
+Priority: optional
+Maintainer: Kosma Moczek <kosma@kosma.pl>
+Build-Depends: debhelper (>= 9)
+Standards-Version: 3.9.5
+Homepage: https://github.com/kosma/runit-docker
+#Vcs-Git: git://anonscm.debian.org/collab-maint/runit-docker.git
+#Vcs-Browser: http://anonscm.debian.org/?p=collab-maint/runit-docker.git;a=summary
+
+Package: runit-docker
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: painlessly use runit in Docker containers
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/copyright b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/copyright
new file mode 100644 (file)
index 0000000..8679a6a
--- /dev/null
@@ -0,0 +1,31 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: runit-docker
+Source: https://github.com/kosma/runit-docker
+
+Files: *
+Copyright: 2015 Kosma Moczek <kosma@kosma.pl>
+License: MIT
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+ * Neither the name of runit-docker nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/docs b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/docs
new file mode 100644 (file)
index 0000000..b43bf86
--- /dev/null
@@ -0,0 +1 @@
+README.md
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/rules b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/rules
new file mode 100755 (executable)
index 0000000..ce15cce
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#DH_VERBOSE = 1
+
+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/default.mk
+
+# see FEATURE AREAS in dpkg-buildflags(1)
+#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+# see ENVIRONMENT in dpkg-buildflags(1)
+# package maintainers to append CFLAGS
+#export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic
+# package maintainers to append LDFLAGS
+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
+
+
+# main packaging script based on dh7 syntax
+%:
+       dh $@ 
+
+# debmake generated override targets
+# This is example for Cmake (See http://bugs.debian.org/641051 )
+#override_dh_auto_configure:
+#      dh_auto_configure -- \
+#      -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
+
+
+
+
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/debian/source/format b/tools/arvbox/lib/arvbox/docker/runit-docker/debian/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker b/tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker
new file mode 100755 (executable)
index 0000000..fdbaad5
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export LD_PRELOAD=/lib/runit-docker.so
+exec runsvdir /etc/service
diff --git a/tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker.c b/tools/arvbox/lib/arvbox/docker/runit-docker/runit-docker.c
new file mode 100644 (file)
index 0000000..825a35f
--- /dev/null
@@ -0,0 +1,32 @@
+#include <signal.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+
+
+int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
+{
+  static int (*real_sigaction)(int signum, const struct sigaction *act, struct sigaction *oldact) = NULL;
+
+  // Retrieve the real sigaction we just shadowed.
+  if (real_sigaction == NULL) {
+    real_sigaction = (void *) dlsym(RTLD_NEXT, "sigaction");
+    // Prevent further shadowing in children.
+    unsetenv("LD_PRELOAD");
+  }
+
+  if (signum == SIGTERM) {
+    // Skip this handler, it doesn't do what we want.
+    return 0;
+  }
+
+  if (signum == SIGHUP) {
+    // Install this handler for others as well.
+    real_sigaction(SIGTERM, act, oldact);
+    real_sigaction(SIGINT, act, oldact);
+  }
+
+  // Forward the call the the real sigaction.
+  return real_sigaction(signum, act, oldact);
+}
+
+// vim: ts=2 sw=2 et
diff --git a/tools/arvbox/lib/arvbox/docker/runsu.sh b/tools/arvbox/lib/arvbox/docker/runsu.sh
new file mode 100755 (executable)
index 0000000..1557d09
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
+HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
+
+flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
+
+export HOME=/var/lib/arvados
+
+if test -z "$1" ; then
+    exec chpst -u arvbox:arvbox:docker $0-service
+else
+    exec chpst -u arvbox:arvbox:docker $@
+fi
diff --git a/tools/arvbox/lib/arvbox/docker/service/api/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/api/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/api/log/run b/tools/arvbox/lib/arvbox/docker/service/api/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/api/run b/tools/arvbox/lib/arvbox/docker/service/api/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/api/run-service b/tools/arvbox/lib/arvbox/docker/service/api/run-service
new file mode 100755 (executable)
index 0000000..058939c
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/bash
+
+exec 2>&1
+set -ex -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/arvados/services/api
+export RAILS_ENV=development
+
+run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
+if ! test -s /var/lib/arvados/api_uuid_prefix ; then
+    ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
+fi
+uuid_prefix=$(cat /var/lib/arvados/api_uuid_prefix)
+
+if ! test -s /var/lib/arvados/api_secret_token ; then
+    ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/api_secret_token
+fi
+secret_token=$(cat /var/lib/arvados/api_secret_token)
+
+if ! test -s /var/lib/arvados/blob_signing_key ; then
+    ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/blob_signing_key
+fi
+blob_signing_key=$(cat /var/lib/arvados/blob_signing_key)
+
+# self signed key will be created by SSO server script.
+test -s /var/lib/arvados/self-signed.key
+
+sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
+
+if test -s /var/lib/arvados/vm-uuid ; then
+    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+else
+    vm_uuid=$uuid_prefix-2x53u-$(ruby -e 'puts rand(2**400).to_s(36)[0,15]')
+    echo $vm_uuid > /var/lib/arvados/vm-uuid
+fi
+
+cat >config/application.yml <<EOF
+development:
+  uuid_prefix: $uuid_prefix
+  secret_token: $secret_token
+  blob_signing_key: $blob_signing_key
+  sso_app_secret: $sso_app_secret
+  sso_app_id: arvados-server
+  sso_provider_url: "https://$localip:${services[sso]}"
+  sso_insecure: true
+  workbench_address: "http://$localip/"
+  git_repo_ssh_base: "git@$localip:"
+  git_repo_https_base: "http://$localip:${services[arv-git-httpd]}/"
+  new_users_are_active: true
+  auto_admin_first_user: true
+  auto_setup_new_users: true
+  auto_setup_new_users_with_vm_uuid: $vm_uuid
+  auto_setup_new_users_with_repository: true
+  default_collection_replication: 1
+EOF
+
+(cd config && /usr/local/lib/arvbox/application_yml_override.py)
+
+if ! test -f /var/lib/arvados/api_database_pw ; then
+    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/api_database_pw
+fi
+database_pw=$(cat /var/lib/arvados/api_database_pw)
+
+if ! (psql postgres -c "\du" | grep "^ arvados ") >/dev/null ; then
+    psql postgres -c "create user arvados with password '$database_pw'"
+    psql postgres -c "ALTER USER arvados CREATEDB;"
+fi
+
+sed "s/password:.*/password: $database_pw/" <config/database.yml.example >config/database.yml
+
+if ! test -f /var/lib/arvados/api_database_setup ; then
+   bundle exec rake db:setup
+   touch /var/lib/arvados/api_database_setup
+fi
+
+if ! test -s /var/lib/arvados/superuser_token ; then
+    bundle exec ./script/create_superuser_token.rb > /var/lib/arvados/superuser_token
+fi
+
+rm -rf tmp
+
+bundle exec rake db:migrate
+
+set +u
+if test "$1" = "--only-setup" ; then
+    exit
+fi
+
+ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=${services[api]} \
+                  --runtime-dir=/var/lib/passenger \
+                  --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+                  --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
new file mode 100755 (executable)
index 0000000..854464e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
+install bin/arv-git-httpd /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export GITOLITE_HTTP_HOME=/var/lib/arvados/git
+export GL_BYPASS_ACCESS_CHECKS=1
+export PATH="$PATH:/var/lib/arvados/git/bin"
+cd ~git
+
+exec /usr/local/bin/arv-git-httpd \
+     -address=:${services[arv-git-httpd]} \
+     -git-command=/usr/share/gitolite3/gitolite-shell \
+     -repo-root=/var/lib/arvados/git/repositories
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run-service b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch-local/run-service
new file mode 100755 (executable)
index 0000000..211b438
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/crunch-run"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/crunch-dispatch-local"
+install bin/crunch-run bin/crunch-dispatch-local /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+
+exec /usr/local/bin/crunch-dispatch-local -crunch-run-command=/usr/local/bin/crunch-run
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run-service b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run-service
new file mode 100755 (executable)
index 0000000..fa3a73a
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/local/lib/arvbox/crunch-setup.sh crunch0
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run-service b/tools/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run-service
new file mode 100755 (executable)
index 0000000..6430e9c
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+sleep 1
+exec /usr/local/lib/arvbox/crunch-setup.sh crunch1
diff --git a/tools/arvbox/lib/arvbox/docker/service/doc/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/doc/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/doc/log/run b/tools/arvbox/lib/arvbox/docker/service/doc/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/doc/run b/tools/arvbox/lib/arvbox/docker/service/doc/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/doc/run-service b/tools/arvbox/lib/arvbox/docker/service/doc/run-service
new file mode 100755 (executable)
index 0000000..1ee62f4
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+exec 2>&1
+set -ex -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/arvados/doc
+run_bundler --without=development
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
+cat <<EOF >/var/lib/arvados/doc-nginx.conf
+worker_processes auto;
+pid /var/lib/arvados/doc-nginx.pid;
+
+error_log stderr;
+daemon off;
+
+events {
+       worker_connections 64;
+}
+
+http {
+     access_log off;
+     include /etc/nginx/mime.types;
+     default_type application/octet-stream;
+     server {
+            listen ${services[doc]} default_server;
+            listen [::]:${services[doc]} default_server;
+            root /usr/src/arvados/doc/.site;
+            index index.html;
+            server_name _;
+     }
+}
+EOF
+
+bundle exec rake generate baseurl=http://$localip:${services[doc]} arvados_api_host=$localip:${services[api]} arvados_workbench_host=http://$localip
+
+exec nginx -c /var/lib/arvados/doc-nginx.conf
diff --git a/tools/arvbox/lib/arvbox/docker/service/docker/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/docker/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/docker/log/run b/tools/arvbox/lib/arvbox/docker/service/docker/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/docker/run b/tools/arvbox/lib/arvbox/docker/service/docker/run
new file mode 100755 (executable)
index 0000000..1ecdc16
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/bash
+
+# Taken from https://github.com/jpetazzo/dind
+
+exec 2>&1
+
+# Ensure that all nodes in /dev/mapper correspond to mapped devices currently loaded by the device-mapper kernel driver
+dmsetup mknodes
+
+: {LOG:=stdio}
+
+# First, make sure that cgroups are mounted correctly.
+CGROUP=/sys/fs/cgroup
+[ -d $CGROUP ] || mkdir $CGROUP
+
+if mountpoint -q $CGROUP ; then
+    break
+else
+    mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP
+fi
+
+if ! mountpoint -q $CGROUP ; then
+    echo "Could not find or mount cgroups. Tried /sys/fs/cgroup and /cgroup.  Did you use --privileged?"
+    exit 1
+fi
+
+if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
+then
+    mount -t securityfs none /sys/kernel/security || {
+        echo "Could not mount /sys/kernel/security."
+        echo "AppArmor detection and --privileged mode might break."
+    }
+fi
+
+# Mount the cgroup hierarchies exactly as they are in the parent system.
+for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
+do
+        [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
+        mountpoint -q $CGROUP/$SUBSYS ||
+                mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
+
+        # The two following sections address a bug which manifests itself
+        # by a cryptic "lxc-start: no ns_cgroup option specified" when
+        # trying to start containers withina container.
+        # The bug seems to appear when the cgroup hierarchies are not
+        # mounted on the exact same directories in the host, and in the
+        # container.
+
+        # Named, control-less cgroups are mounted with "-o name=foo"
+        # (and appear as such under /proc/<pid>/cgroup) but are usually
+        # mounted on a directory named "foo" (without the "name=" prefix).
+        # Systemd and OpenRC (and possibly others) both create such a
+        # cgroup. To avoid the aforementioned bug, we symlink "foo" to
+        # "name=foo". This shouldn't have any adverse effect.
+        echo $SUBSYS | grep -q ^name= && {
+                NAME=$(echo $SUBSYS | sed s/^name=//)
+                ln -s $SUBSYS $CGROUP/$NAME
+        }
+
+        # Likewise, on at least one system, it has been reported that
+        # systemd would mount the CPU and CPU accounting controllers
+        # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
+        # but on a directory called "cpu,cpuacct" (note the inversion
+        # in the order of the groups). This tries to work around it.
+        [ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
+done
+
+# Note: as I write those lines, the LXC userland tools cannot setup
+# a "sub-container" properly if the "devices" cgroup is not in its
+# own hierarchy. Let's detect this and issue a warning.
+grep -q :devices: /proc/1/cgroup ||
+       echo "WARNING: the 'devices' cgroup should be in its own hierarchy."
+grep -qw devices /proc/1/cgroup ||
+       echo "WARNING: it looks like the 'devices' cgroup is not mounted."
+
+# Now, close extraneous file descriptors.
+pushd /proc/self/fd >/dev/null
+for FD in *
+do
+       case "$FD" in
+       # Keep stdin/stdout/stderr
+       [012])
+               ;;
+       # Nuke everything else
+       *)
+               eval exec "$FD>&-"
+               ;;
+       esac
+done
+popd >/dev/null
+
+
+# If a pidfile is still around (for example after a container restart),
+# delete it so that docker can start.
+rm -rf /var/run/docker.pid
+
+read pid cmd state ppid pgrp session tty_nr tpgid rest < /proc/self/stat
+trap "kill -TERM -$pgrp; exit" EXIT TERM KILL SIGKILL SIGTERM SIGQUIT
+
+if ! docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS ; then
+    docker daemon $DOCKER_DAEMON_ARGS
+fi
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/run b/tools/arvbox/lib/arvbox/docker/service/gitolite/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/run-service b/tools/arvbox/lib/arvbox/docker/service/gitolite/run-service
new file mode 100755 (executable)
index 0000000..e0e8771
--- /dev/null
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/git
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+
+export USER=git
+export USERNAME=git
+export LOGNAME=git
+export HOME=/var/lib/arvados/git
+
+cd ~arvbox
+
+mkdir -p ~arvbox/.ssh ~git/.ssh
+chmod 0700 ~arvbox/.ssh ~git/.ssh
+
+if ! test -s ~arvbox/.ssh/id_rsa ; then
+    ssh-keygen -t rsa -P '' -f .ssh/id_rsa
+    cp ~arvbox/.ssh/id_rsa ~arvbox/.ssh/id_rsa.pub ~git/.ssh
+fi
+
+if test -s ~arvbox/.ssh/known_hosts ; then
+    ssh-keygen -f ".ssh/known_hosts" -R localhost
+fi
+
+if ! test -f /var/lib/arvados/gitolite-setup ; then
+    cd ~git
+
+    # Do a no-op login to populate known_hosts
+    # with the hostkey, so it won't try to ask
+    # about it later.
+    cp .ssh/id_rsa.pub .ssh/authorized_keys
+    ssh -o stricthostkeychecking=no git@localhost true
+    rm .ssh/authorized_keys
+
+    cp /usr/local/lib/arvbox/gitolite.rc .gitolite.rc
+
+    gitolite setup -pk .ssh/id_rsa.pub
+
+    if ! test -d gitolite-admin ; then
+        git clone git@localhost:gitolite-admin
+    fi
+
+    cd gitolite-admin
+    git config user.email arvados
+    git config user.name arvados
+    git config push.default simple
+    git push
+
+    touch /var/lib/arvados/gitolite-setup
+else
+    # Do a no-op login to populate known_hosts
+    # with the hostkey, so it won't try to ask
+    # about it later.  Don't run anything,
+    # get the default gitolite behavior.
+    ssh -o stricthostkeychecking=no git@localhost
+fi
+
+prefix=$(arv --format=uuid user current | cut -d- -f1)
+
+if ! test -s /var/lib/arvados/arvados-git-uuid ; then
+    repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
+    echo $repo_uuid > /var/lib/arvados/arvados-git-uuid
+fi
+
+repo_uuid=$(cat /var/lib/arvados/arvados-git-uuid)
+
+if ! test -s /var/lib/arvados/arvados-git-link-uuid ; then
+    all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
+
+    set +e
+    read -rd $'\000' newlink <<EOF
+{
+ "tail_uuid":"$all_users_group_uuid",
+ "head_uuid":"$repo_uuid",
+ "link_class":"permission",
+ "name":"can_read"
+}
+EOF
+    set -e
+    link_uuid=$(arv --format=uuid link create --link "$newlink")
+    echo $link_uuid > /var/lib/arvados/arvados-git-link-uuid
+fi
+
+if ! test -d /var/lib/arvados/git/repositories/$repo_uuid.git ; then
+    git clone --bare /usr/src/arvados /var/lib/arvados/git/repositories/$repo_uuid.git
+else
+    git --git-dir=/var/lib/arvados/git/repositories/$repo_uuid.git fetch -f /usr/src/arvados master:master
+fi
+
+cd /usr/src/arvados/services/api
+export RAILS_ENV=development
+
+git_user_key=$(cat ~git/.ssh/id_rsa.pub)
+
+cat > config/arvados-clients.yml <<EOF
+development:
+  gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
+  gitolite_tmp: /var/lib/arvados/git
+  arvados_api_host: $localip:${services[api]}
+  arvados_api_token: "$ARVADOS_API_TOKEN"
+  arvados_api_host_insecure: true
+  gitolite_arvados_git_user_key: "$git_user_key"
+EOF
+
+while true ; do
+    bundle exec script/arvados-git-sync.rb development
+    sleep 120
+done
diff --git a/tools/arvbox/lib/arvbox/docker/service/keep-web/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/keep-web/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/keep-web/log/run b/tools/arvbox/lib/arvbox/docker/service/keep-web/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keep-web/run b/tools/arvbox/lib/arvbox/docker/service/keep-web/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keep-web/run-service b/tools/arvbox/lib/arvbox/docker/service/keep-web/run-service
new file mode 100755 (executable)
index 0000000..a2c6aa1
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keep-web"
+install bin/keep-web /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+
+exec /usr/local/bin/keep-web -trust-all-content -listen=:${services[keep-web]}
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepproxy/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/keepproxy/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepproxy/log/run b/tools/arvbox/lib/arvbox/docker/service/keepproxy/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepproxy/run b/tools/arvbox/lib/arvbox/docker/service/keepproxy/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepproxy/run-service b/tools/arvbox/lib/arvbox/docker/service/keepproxy/run-service
new file mode 100755 (executable)
index 0000000..413a67e
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+exec 2>&1
+sleep 2
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keepproxy"
+install bin/keepproxy /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+
+set +e
+read -rd $'\000' keepservice <<EOF
+{
+ "service_host":"$localip",
+ "service_port":${services[keepproxy]},
+ "service_ssl_flag":false,
+ "service_type":"proxy"
+}
+EOF
+set -e
+
+if test -s /var/lib/arvados/keepproxy-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
+    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
+else
+    UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
+    echo $UUID > /var/lib/arvados/keepproxy-uuid
+fi
+
+exec /usr/local/bin/keepproxy -listen=:${services[keepproxy]}
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore0/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/keepstore0/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore0/log/run b/tools/arvbox/lib/arvbox/docker/service/keepstore0/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore0/run b/tools/arvbox/lib/arvbox/docker/service/keepstore0/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore0/run-service b/tools/arvbox/lib/arvbox/docker/service/keepstore0/run-service
new file mode 100755 (executable)
index 0000000..cf411e4
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+. /usr/local/lib/arvbox/common.sh
+exec /usr/local/lib/arvbox/keep-setup.sh keep0 ${services[keepstore0]}
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore1/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/keepstore1/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore1/log/run b/tools/arvbox/lib/arvbox/docker/service/keepstore1/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore1/run b/tools/arvbox/lib/arvbox/docker/service/keepstore1/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/keepstore1/run-service b/tools/arvbox/lib/arvbox/docker/service/keepstore1/run-service
new file mode 100755 (executable)
index 0000000..8d34d06
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+. /usr/local/lib/arvbox/common.sh
+exec /usr/local/lib/arvbox/keep-setup.sh keep1 ${services[keepstore1]}
diff --git a/tools/arvbox/lib/arvbox/docker/service/postgres/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/postgres/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/postgres/log/run b/tools/arvbox/lib/arvbox/docker/service/postgres/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/postgres/run b/tools/arvbox/lib/arvbox/docker/service/postgres/run
new file mode 100755 (executable)
index 0000000..4918bd7
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
+
+. /usr/local/lib/arvbox/common.sh
+
+chown -R $PGUSER:$PGGROUP /var/lib/postgresql
+chown -R $PGUSER:$PGGROUP /var/run/postgresql
+chown -R $PGUSER:$PGGROUP /etc/postgresql
+chown -R $PGUSER:$PGGROUP /etc/ssl/private
+
+exec chpst -u $PGUSER:$PGGROUP $0-service
diff --git a/tools/arvbox/lib/arvbox/docker/service/postgres/run-service b/tools/arvbox/lib/arvbox/docker/service/postgres/run-service
new file mode 100755 (executable)
index 0000000..a05be62
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+if ! test -d /var/lib/postgresql/9.4/main ; then
+    /usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main
+    sh -c "while ! (psql postgres -c'\du' | grep '^ arvbox ') >/dev/null ; do createuser -s arvbox ; sleep 1 ; done" &
+fi
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+
+rm -f /var/lib/postgresql/9.4/main/postmaster.pid
+
+exec /usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf
diff --git a/tools/arvbox/lib/arvbox/docker/service/ready/run b/tools/arvbox/lib/arvbox/docker/service/ready/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/ready/run-service b/tools/arvbox/lib/arvbox/docker/service/ready/run-service
new file mode 100755 (executable)
index 0000000..977f612
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/bash
+
+. /usr/local/lib/arvbox/common.sh
+
+set -eu -o pipefail
+
+if ! [[ -d /tmp/arvbox-ready ]] ; then
+   echo
+   echo "Arvados-in-a-box starting"
+   echo
+   echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes (or longer) to download and"
+   echo "install dependencies.  Use \"arvbox log\" to monitor the progress of specific services."
+   echo
+   mkdir -p /tmp/arvbox-ready
+fi
+
+sleep 3
+
+waiting=""
+
+for s in "${!services[@]}"
+do
+  if ! [[ -f /tmp/arvbox-ready/$s ]] ; then
+    if nc -z localhost ${services[$s]} ; then
+      echo "$s is ready at $localip:${services[$s]}"
+      touch /tmp/arvbox-ready/$s
+    else
+      waiting="$waiting $s"
+    fi
+  fi
+done
+
+if ! docker version >/dev/null 2>/dev/null ; then
+  waiting="$waiting docker"
+fi
+
+for sdk_app in arv arv-get cwl-runner arv-mount ; do
+    if ! which $sdk_app >/dev/null ; then
+        waiting="$waiting sdk"
+        break
+    fi
+done
+
+if ! (ps x | grep -v grep | grep "crunch-dispatch") > /dev/null ; then
+    waiting="$waiting crunch-dispatch"
+fi
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+
+vm_ok=0
+if test -s /var/lib/arvados/vm-uuid -a -s /var/lib/arvados/superuser_token; then
+    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+    export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+    if (which arv && arv virtual_machine get --uuid $vm_uuid) >/dev/null 2>/dev/null ; then
+        vm_ok=1
+    fi
+fi
+
+if test $vm_ok = 0 ; then
+    waiting="$waiting vm"
+fi
+
+if ! [[ -z "$waiting" ]] ; then
+    if ps x | grep -v grep | grep "bundle install" > /dev/null; then
+        gemcount=$(ls /var/lib/gems/ruby/2.1.0/gems 2>/dev/null | wc -l)
+
+        gemlockcount=0
+        for l in /usr/src/arvados/services/api/Gemfile.lock \
+                     /usr/src/arvados/apps/workbench/Gemfile.lock \
+                     /usr/src/sso/Gemfile.lock ; do
+            gc=$(cat $l \
+                        | grep -vE "(GEM|PLATFORMS|DEPENDENCIES|$^|remote:|specs:)" \
+                        | sed 's/^ *//' | sed 's/(.*)//' | sed 's/ *$//' | sort | uniq | wc -l)
+            gemlockcount=$(($gemlockcount + $gc))
+        done
+        waiting="$waiting (installing ruby gems $gemcount/$gemlockcount)"
+    fi
+
+    if ps x | grep -v grep | grep "c++.*/var/lib/passenger" > /dev/null ; then
+        waiting="$waiting (compiling passenger)"
+    fi
+
+    if ps x | grep -v grep | grep "pip install" > /dev/null; then
+        waiting="$waiting (installing python packages)"
+    fi
+    echo "    Waiting for$waiting ..."
+    exit 1
+fi
+
+echo
+echo "Your Arvados-in-a-box is ready!"
+echo "Workbench is running at http://$localip"
+
+rm -r /tmp/arvbox-ready
+
+sv stop ready >/dev/null
diff --git a/tools/arvbox/lib/arvbox/docker/service/sdk/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/sdk/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/sdk/log/run b/tools/arvbox/lib/arvbox/docker/service/sdk/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/sdk/run b/tools/arvbox/lib/arvbox/docker/service/sdk/run
new file mode 100755 (executable)
index 0000000..816b166
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+
+/usr/local/lib/arvbox/runsu.sh $0-service
+sv stop sdk
diff --git a/tools/arvbox/lib/arvbox/docker/service/sdk/run-service b/tools/arvbox/lib/arvbox/docker/service/sdk/run-service
new file mode 100755 (executable)
index 0000000..3ee6f2a
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+mkdir -p ~/.pip /var/lib/arvados/pip
+cat > ~/.pip/pip.conf <<EOF
+[global]
+download_cache = /var/lib/arvados/pip
+EOF
+
+cd /usr/src/arvados/sdk/cli
+run_bundler --binstubs=$PWD/binstubs
+ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/local/bin/arv
+
+cd /usr/src/arvados/sdk/python
+python setup.py sdist
+pip_install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
+
+cd /usr/src/arvados/sdk/cwl
+python setup.py sdist
+pip_install $(ls dist/arvados-cwl-runner-*.tar.gz | tail -n1)
+
+cd /usr/src/arvados/services/fuse
+python setup.py sdist
+pip_install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmctld/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/slurmctld/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmctld/log/run b/tools/arvbox/lib/arvbox/docker/service/slurmctld/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmctld/run b/tools/arvbox/lib/arvbox/docker/service/slurmctld/run
new file mode 100755 (executable)
index 0000000..bd75bd0
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cat > /etc/slurm-llnl/slurm.conf  <<EOF
+ControlMachine=$HOSTNAME
+ControlAddr=$HOSTNAME
+AuthType=auth/munge
+DefaultStorageLoc=/var/log/slurm-llnl
+SelectType=select/cons_res
+SelectTypeParameters=CR_CPU_Memory
+SlurmUser=arvbox
+SlurmdUser=arvbox
+SlurmctldPort=7002
+SlurmctldTimeout=300
+SlurmdPort=7003
+SlurmdSpoolDir=/var/tmp/slurmd.spool
+SlurmdTimeout=300
+StateSaveLocation=/var/tmp/slurm.state
+NodeName=$HOSTNAME
+PartitionName=compute State=UP Default=YES Nodes=$HOSTNAME
+EOF
+
+mkdir -p /var/run/munge
+
+/usr/sbin/munged -f
+
+exec /usr/sbin/slurmctld -v -D
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmd/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/slurmd/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmd/log/run b/tools/arvbox/lib/arvbox/docker/service/slurmd/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/slurmd/run b/tools/arvbox/lib/arvbox/docker/service/slurmd/run
new file mode 100755 (executable)
index 0000000..865a7e6
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+exec /usr/local/lib/arvbox/runsu.sh /usr/sbin/slurmd -v -D
diff --git a/tools/arvbox/lib/arvbox/docker/service/ssh/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/ssh/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/ssh/log/run b/tools/arvbox/lib/arvbox/docker/service/ssh/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/ssh/run b/tools/arvbox/lib/arvbox/docker/service/ssh/run
new file mode 100755 (executable)
index 0000000..0f23542
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+exec 2>&1
+set -eux -o pipefail
+
+if ! test -d /var/run/sshd ; then
+   mkdir /var/run/sshd
+   chmod 0755 /var/run/sshd
+fi
+/usr/sbin/sshd -D
diff --git a/tools/arvbox/lib/arvbox/docker/service/sso/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/sso/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/sso/log/run b/tools/arvbox/lib/arvbox/docker/service/sso/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/sso/run b/tools/arvbox/lib/arvbox/docker/service/sso/run
new file mode 120000 (symlink)
index 0000000..a388c8b
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/sso/run-service b/tools/arvbox/lib/arvbox/docker/service/sso/run-service
new file mode 100755 (executable)
index 0000000..da413e0
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+exec 2>&1
+set -ex -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/sso
+export RAILS_ENV=development
+
+run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
+if ! test -s /var/lib/arvados/sso_uuid_prefix ; then
+  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/sso_uuid_prefix
+fi
+uuid_prefix=$(cat /var/lib/arvados/sso_uuid_prefix)
+
+if ! test -s /var/lib/arvados/sso_secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_secret_token
+fi
+secret_token=$(cat /var/lib/arvados/sso_secret_token)
+
+if ! test -s /var/lib/arvados/self-signed.key ; then
+  openssl req -new -x509 -nodes -out /var/lib/arvados/self-signed.pem -keyout /var/lib/arvados/self-signed.key -days 365 -subj '/CN=localhost'
+fi
+
+cat >config/application.yml <<EOF
+development:
+  uuid_prefix: $uuid_prefix
+  secret_token: $secret_token
+  default_link_url: "http://$localip"
+  allow_account_registration: true
+EOF
+
+(cd config && /usr/local/lib/arvbox/application_yml_override.py)
+
+if ! test -f /var/lib/arvados/sso_database_pw ; then
+    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/sso_database_pw
+fi
+database_pw=$(cat /var/lib/arvados/sso_database_pw)
+
+if ! (psql postgres -c "\du" | grep "^ arvados_sso ") >/dev/null ; then
+    psql postgres -c "create user arvados_sso with password '$database_pw'"
+    psql postgres -c "ALTER USER arvados_sso CREATEDB;"
+fi
+
+sed "s/password:.*/password: $database_pw/" <config/database.yml.example >config/database.yml
+
+if ! test -f /var/lib/arvados/sso_database_setup ; then
+   bundle exec rake db:setup
+
+   if ! test -s /var/lib/arvados/sso_app_secret ; then
+       ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_app_secret
+   fi
+   app_secret=$(cat /var/lib/arvados/sso_app_secret)
+
+   bundle exec rails console <<EOF
+c = Client.new
+c.name = "joshid"
+c.app_id = "arvados-server"
+c.app_secret = "$app_secret"
+c.save!
+EOF
+
+   touch /var/lib/arvados/sso_database_setup
+fi
+
+rm -rf tmp
+
+bundle exec rake db:migrate
+
+set +u
+if test "$1" = "--only-setup" ; then
+    exit
+fi
+
+exec bundle exec passenger start --port=${services[sso]} \
+     --runtime-dir=/var/lib/passenger \
+     --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+     --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/tools/arvbox/lib/arvbox/docker/service/vm/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/vm/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/vm/log/run b/tools/arvbox/lib/arvbox/docker/service/vm/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/vm/run b/tools/arvbox/lib/arvbox/docker/service/vm/run
new file mode 100755 (executable)
index 0000000..0c10cb0
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+set -e
+
+. /usr/local/lib/arvbox/common.sh
+
+git config --system "credential.http://$localip:${services[arv-git-httpd]}/.username" none
+git config --system "credential.http://$localip:${services[arv-git-httpd]}/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
+
+/usr/local/lib/arvbox/runsu.sh $0-service
+
+cd /usr/src/arvados/services/login-sync
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+export ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+
+while true ; do
+      bundle exec arvados-login-sync
+      sleep 120
+done
diff --git a/tools/arvbox/lib/arvbox/docker/service/vm/run-service b/tools/arvbox/lib/arvbox/docker/service/vm/run-service
new file mode 100755 (executable)
index 0000000..fb209f5
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+exec 2>&1
+sleep 2
+set -ex -o pipefail
+
+. /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/arvados/services/login-sync
+run_bundler
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+export ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+
+set +e
+read -rd $'\000' vm <<EOF
+{
+ "uuid": "$ARVADOS_VIRTUAL_MACHINE_UUID",
+ "hostname":"$localip"
+}
+EOF
+set -e
+
+if arv virtual_machine get --uuid $ARVADOS_VIRTUAL_MACHINE_UUID ; then
+    arv virtual_machine update --uuid $ARVADOS_VIRTUAL_MACHINE_UUID --virtual-machine "$vm"
+else
+    arv virtual_machine create --virtual-machine "$vm"
+fi
diff --git a/tools/arvbox/lib/arvbox/docker/service/workbench/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/workbench/log/main/.gitstub
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/arvbox/lib/arvbox/docker/service/workbench/log/run b/tools/arvbox/lib/arvbox/docker/service/workbench/log/run
new file mode 120000 (symlink)
index 0000000..d6aef4a
--- /dev/null
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/workbench/run b/tools/arvbox/lib/arvbox/docker/service/workbench/run
new file mode 100755 (executable)
index 0000000..6ac0476
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+set -e
+
+/usr/local/lib/arvbox/runsu.sh $0-service $1
+
+cd /usr/src/arvados/apps/workbench
+
+rm -rf tmp
+mkdir tmp
+chown arvbox:arvbox tmp
+
+if test "$1" != "--only-deps" ; then
+    exec bundle exec passenger start --port 80 \
+         --user arvbox --runtime-dir=/var/lib/passenger
+fi
diff --git a/tools/arvbox/lib/arvbox/docker/service/workbench/run-service b/tools/arvbox/lib/arvbox/docker/service/workbench/run-service
new file mode 100755 (executable)
index 0000000..850022a
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+exec 2>&1
+set -ex -o pipefail
+
+.  /usr/local/lib/arvbox/common.sh
+
+cd /usr/src/arvados/apps/workbench
+export RAILS_ENV=development
+
+run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
+if ! test -s /var/lib/arvados/workbench_secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/workbench_secret_token
+fi
+secret_token=$(cat /var/lib/arvados/workbench_secret_token)
+
+if ! test -s self-signed.key ; then
+  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
+fi
+
+cat >config/application.yml <<EOF
+development:
+  secret_token: $secret_token
+  arvados_login_base: https://$localip:${services[api]}/login
+  arvados_v1_base: https://$localip:${services[api]}/arvados/v1
+  arvados_insecure_https: true
+  keep_web_download_url: http://$localip:${services[keep-web]}/c=%{uuid_or_pdh}
+  keep_web_url: http://$localip:${services[keep-web]}/c=%{uuid_or_pdh}
+  arvados_docsite: http://$localip:${services[doc]}/
+EOF
+
+(cd config && /usr/local/lib/arvbox/application_yml_override.py)
diff --git a/tools/arvbox/lib/arvbox/docker/waitforpostgres.sh b/tools/arvbox/lib/arvbox/docker/waitforpostgres.sh
new file mode 100755 (executable)
index 0000000..84d9904
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+while ! psql postgres -c\\du >/dev/null 2>/dev/null ; do
+    sleep 1
+done
index f422501b10ff1858f9b636621aaaba4bad662d5b..2ac12abcba23e381073589cf209915b88a9d8cef 100644 (file)
@@ -21,6 +21,11 @@ from crunchstat_summary import logger
 AVAILABLE_RAM_RATIO = 0.95
 
 
+# Workaround datetime.datetime.strptime() thread-safety bug by calling
+# it once before starting threads.  https://bugs.python.org/issue7980
+datetime.datetime.strptime('1999-12-31_23:59:59', '%Y-%m-%d_%H:%M:%S')
+
+
 class Task(object):
     def __init__(self):
         self.starttime = None